JSON Providers
Flask uses a pluggable JSON provider system to handle serialization and deserialization. This architecture allows you to customize how JSON is processed, add support for custom data types, or replace the underlying JSON library entirely without changing your application's route logic.
The application's JSON behavior is managed by an instance of JSONProvider stored on app.json. When you call flask.jsonify or use flask.json.dumps, Flask delegates the work to this provider.
The Default JSON Provider
By default, Flask uses the DefaultJSONProvider (found in flask.json.provider). This provider uses Python's built-in json library but extends it to support common web development data types that the standard library cannot handle.
The DefaultJSONProvider automatically serializes:
datetime.datetimeanddatetime.date: Converted to RFC 822 strings (the standard HTTP date format) usingwerkzeug.http.http_date.uuid.UUID: Converted to a string.dataclasses.dataclass: Converted to a dictionary usingdataclasses.asdict.Markup(or any object with an__html__method): Calls the method to get a string, which is useful for returning pre-rendered HTML fragments in JSON.
Configuring the Default Provider
You can modify the behavior of the default provider by setting attributes on app.json. For example, if your API requires a specific media type like JSON API, you can change the mimetype attribute:
from flask import Flask, jsonify
app = Flask(__name__)
app.json.mimetype = "application/vnd.api+json"
@app.route("/data")
def get_data():
# The response will have Content-Type: application/vnd.api+json
return jsonify({"message": "Hello World"})
Other configurable attributes on DefaultJSONProvider include:
ensure_ascii: Defaults toTrue. Set toFalseto output UTF-8 characters directly instead of escape sequences.sort_keys: Defaults toTrue. Ensures consistent output by sorting dictionary keys.compact: Controls whether the output includes extra whitespace. By default (None), it uses a compact representation in production and an indented, readable format in debug mode.
Customizing Serialization
If you need to support a custom class that Flask doesn't recognize, you can provide a custom function to the default attribute. This function is called for any object the provider doesn't know how to serialize.
from flask import Flask
from flask.json.provider import DefaultJSONProvider
class User:
def __init__(self, username):
self.username = username
def custom_default(obj):
if isinstance(obj, User):
return {"username": obj.username}
return DefaultJSONProvider.default(obj)
app = Flask(__name__)
app.json.default = custom_default
Internally, DefaultJSONProvider.dumps passes this function to json.dumps(default=...). The _default function in flask.json.provider serves as the base implementation that handles the standard Flask extensions like UUIDs and dates.
Implementing a Custom Provider
For more significant changes, such as using a high-performance library like orjson or ujson, you can implement a completely new provider by subclassing JSONProvider.
A custom provider must implement at least dumps and loads. You can also override response to change how the flask.Response object is constructed.
import orjson
from flask import Flask
from flask.json.provider import JSONProvider
class OrjsonProvider(JSONProvider):
def dumps(self, obj, **kwargs):
# orjson returns bytes, but JSONProvider.dumps expects a string
return orjson.dumps(obj, **kwargs).decode()
def loads(self, s, **kwargs):
return orjson.loads(s, **kwargs)
class CustomFlask(Flask):
json_provider_class = OrjsonProvider
app = CustomFlask(__name__)
When you define a custom provider class, set it as json_provider_class on your Flask subclass. Flask instantiates this class during application setup, passing the app instance to the provider's constructor. The provider maintains a weak reference to the app via self._app.
Internal Mechanism and jsonify
The flask.jsonify function is a wrapper around app.json.response. The JSONProvider.response method handles the logic of serializing the data and wrapping it in the application's response_class.
One specific constraint in JSONProvider._prepare_response_obj is that jsonify (and the provider's response method) cannot accept both positional and keyword arguments at the same time.
# This works (positional)
jsonify([1, 2, 3])
# This works (keyword)
jsonify(id=1, name="Flask")
# This raises a TypeError
jsonify([1, 2, 3], id=1)
The DefaultJSONProvider.response implementation also ensures that a newline is appended to the end of the response body, which is a common convention for JSON APIs to make them easier to use with command-line tools like curl.