JSON Providers and Customization
Flask abstracts all JSON operations through a provider interface. This design allows applications to customize how data is serialized and deserialized, or even replace the underlying JSON library entirely, while maintaining a consistent API across the framework.
The JSONProvider Interface
The base JSONProvider class, defined in src/flask/json/provider.py, establishes the contract for JSON operations within a Flask application. It maintains a weak reference to the application instance via self._app.
The interface defines five primary methods:
dumps(obj, **kwargs): Serializes an object to a JSON string.loads(s, **kwargs): Deserializes a JSON string or bytes to a Python object.dump(obj, fp, **kwargs): Serializes an object and writes it to a file-like object.load(fp, **kwargs): Reads from a file-like object and deserializes the content.response(*args, **kwargs): Creates aResponseobject containing the JSON-serialized data.
The response method is what powers the flask.json.jsonify function. It uses an internal helper, _prepare_response_obj, to ensure that either positional arguments or keyword arguments are provided, but not both.
# Example of the response logic in JSONProvider
def response(self, *args: t.Any, **kwargs: t.Any) -> Response:
obj = self._prepare_response_obj(args, kwargs)
return self._app.response_class(self.dumps(obj), mimetype="application/json")
Default JSON Behavior
The DefaultJSONProvider is the standard implementation used by Flask. It wraps Python's built-in json module but extends it to support common Python types that are not JSON-serializable by default.
Extended Type Support
The DefaultJSONProvider uses a custom _default function to handle the following types:
- Dates and Datetimes: Serialized to RFC 822 strings (HTTP date format) using
werkzeug.http.http_date. - UUIDs and Decimals: Serialized to their string representations.
- Dataclasses: Automatically converted to dictionaries using
dataclasses.asdict. - Markup: Objects with an
__html__method (like those from MarkupSafe) are converted to strings.
Configuration Attributes
You can configure the behavior of the default provider by modifying its attributes on the app.json instance:
ensure_ascii: Defaults toTrue. If set toFalse, non-ASCII characters are not escaped.sort_keys: Defaults toTrue. Ensures that dictionary keys are sorted alphabetically in the output.compact: Controls whether the output includes indentation and spaces. By default, it isNone, which enables pretty-printing in debug mode and compact output otherwise.mimetype: The content type used for responses, defaulting toapplication/json.
Customizing JSON Operations
There are two primary ways to customize JSON behavior in this codebase: modifying the existing provider or replacing it with a custom subclass.
Modifying the Default Provider
For simple changes, you can modify the attributes of the existing provider instance on the application object:
app = Flask(__name__)
app.json.sort_keys = False
app.json.ensure_ascii = False
Implementing a Custom Provider
For more advanced customization, such as adding an object_hook for deserialization or using a different library like orjson, you can subclass DefaultJSONProvider.
The following example, adapted from tests/test_json.py, demonstrates how to implement a custom object_hook:
from flask.json.provider import DefaultJSONProvider
class CustomProvider(DefaultJSONProvider):
def object_hook(self, obj):
if len(obj) == 1 and "_foo" in obj:
return CustomType(obj["_foo"])
return obj
def loads(self, s, **kwargs):
kwargs.setdefault("object_hook", self.object_hook)
return super().loads(s, **kwargs)
app = Flask(__name__)
app.json = CustomProvider(app)
To use a custom provider globally across all instances of a Flask subclass, you can set the json_provider_class attribute:
class MyFlask(Flask):
json_provider_class = CustomProvider
Global Delegation
The top-level functions in the flask.json module (dumps, loads, jsonify, etc.) are designed to delegate to the current application's provider if an application context is active.
As seen in src/flask/json/__init__.py, these functions check for current_app:
def dumps(obj: t.Any, **kwargs: t.Any) -> str:
if current_app:
return current_app.json.dumps(obj, **kwargs)
# Fallback if no app context is active
kwargs.setdefault("default", _default)
return _json.dumps(obj, **kwargs)
This ensures that any customization applied to app.json is respected by all JSON operations performed within the context of that application, including those triggered by jsonify or when returning a dict from a view function.