JSON & Serialization
Flask provides a flexible JSON serialization system centered around the JSONProvider architecture. This design allows the framework to decouple its core logic from specific JSON libraries while providing a robust default implementation that handles common Python types.
The JSON Provider Architecture
Flask 2.2 introduced the JSONProvider base class in src/flask/json/provider.py to manage how an application handles JSON data. By default, every Flask application uses an instance of DefaultJSONProvider, which is accessible via app.json.
The provider pattern allows you to swap the underlying JSON library (for example, to use orjson or ujson for performance) by either setting json_provider_class on a Flask subclass or replacing app.json directly.
from flask import Flask
from flask.json.provider import DefaultJSONProvider
class CustomProvider(DefaultJSONProvider):
def dumps(self, obj, **kwargs):
# Custom serialization logic
return super().dumps(obj, **kwargs)
app = Flask(__name__)
app.json = CustomProvider(app)
Default Serialization Behavior
The DefaultJSONProvider uses Python's built-in json module but extends it to support types frequently used in web development. The _default function in src/flask/json/provider.py handles:
- Dates and Datetimes: Serialized to RFC 822 strings using
werkzeug.http.http_date. - UUIDs and Decimals: Converted to their string representations.
- Dataclasses: Converted to dictionaries using
dataclasses.asdict. - Markup: Objects with an
__html__method (likemarkupsafe.Markup) are converted to strings.
The provider also manages formatting via the compact attribute. In src/flask/json/provider.py, the response method checks app.debug: if the app is in debug mode or compact is set to False, the JSON output is pretty-printed with an indentation of 2 spaces.
The jsonify Response Pattern
The flask.json.jsonify function is the standard way to return JSON from a view. It delegates the work to app.json.response(). A critical constraint in JSONProvider._prepare_response_obj is that jsonify accepts either positional arguments or keyword arguments, but never both simultaneously.
# Valid: positional argument
return jsonify([1, 2, 3])
# Valid: keyword arguments (becomes a dict)
return jsonify(id=1, name="Flask")
# Invalid: raises TypeError
return jsonify({"id": 1}, status="active")
If multiple positional arguments are provided, they are treated as a list. If no arguments are provided, None is serialized.
Lossless Serialization with Tagging
Standard JSON is "lossy"—for example, it cannot distinguish between a list and a tuple after a serialization round-trip. To solve this for sensitive data like session cookies, Flask uses TaggedJSONSerializer in src/flask/json/tag.py.
This serializer uses a "tag" system where non-standard types are wrapped in a dictionary with a specific key prefix. For example, a tuple is tagged with " t".
Supported Tags
The TaggedJSONSerializer includes default tags for:
dict(tagged as" di"if it contains a key that looks like a tag)tuple(tagged as" t")bytes(tagged as" b"and base64 encoded)UUID(tagged as" u")datetime(tagged as" d")
Implementing Custom Tags
You can extend the session's serialization capabilities by subclassing JSONTag and registering it with the serializer. This is useful for custom objects that must persist across requests in the session.
As seen in tests/test_json_tag.py, a custom tag requires implementing check, to_json, and to_python:
from flask.json.tag import JSONTag
class TagFoo(JSONTag):
__slots__ = ()
key = " f" # The tag identifier
def check(self, value):
# Determine if this value should be handled by this tag
return isinstance(value, Foo)
def to_json(self, value):
# Convert the object to a JSON-serializable format
return self.serializer.tag(value.data)
def to_python(self, value):
# Reconstruct the object from the JSON format
return Foo(value)
# Registering the tag for use in sessions
app.session_interface.serializer.register(TagFoo)
When registering tags, the order matters. If a custom tag is a specialized version of an existing type (like an OrderedDict vs a standard dict), it should be registered with index=0 to ensure it is checked before the more general tag. This logic is handled by the register method in TaggedJSONSerializer, which maintains an ordered list of tags in self.order.