Implementing Custom JSON Providers
By the end of this tutorial, you will have built a custom JSON provider that can serialize and deserialize a custom Python class. This allows you to seamlessly use your own data types with Flask's jsonify and request.get_json() functions.
Prerequisites
- Flask installed in your environment.
- A basic understanding of Python classes and inheritance.
Step 1: Define a Custom Class
First, create a simple class that represents data in your application. By default, Python's json library (and Flask's default provider) won't know how to serialize this.
class User:
def __init__(self, username: str, email: str):
self.username = username
self.email = email
def __repr__(self):
return f"<User {self.username}>"
Step 2: Subclass DefaultJSONProvider
To add support for the User class, you should subclass DefaultJSONProvider from flask.json.provider. This allows you to keep all the standard Flask JSON features (like handling datetimes and UUIDs) while adding your own logic.
from flask.json.provider import DefaultJSONProvider
import typing as t
class CustomJSONProvider(DefaultJSONProvider):
def default(self, o: t.Any) -> t.Any:
if isinstance(o, User):
return {"_type": "User", "username": o.username, "email": o.email}
return super().default(o)
By overriding the default method, you tell the provider how to convert a User instance into a JSON-serializable format (a dictionary). If the object is not a User, we fall back to the standard behavior using super().default(o).
Step 3: Customize Deserialization
To turn JSON back into User objects when receiving data, override the loads method and provide an object_hook.
import json
class CustomJSONProvider(DefaultJSONProvider):
def default(self, o: t.Any) -> t.Any:
if isinstance(o, User):
return {"_type": "User", "username": o.username, "email": o.email}
return super().default(o)
def object_hook(self, obj: dict[str, t.Any]) -> t.Any:
if obj.get("_type") == "User":
return User(username=obj["username"], email=obj["email"])
return obj
def loads(self, s: str | bytes, **kwargs: t.Any) -> t.Any:
kwargs.setdefault("object_hook", self.object_hook)
return super().loads(s, **kwargs)
The object_hook is a standard json library feature that Flask's JSONProvider exposes. It is called for every dictionary decoded from JSON.
Step 4: Register the Provider
You can register your custom provider by assigning an instance of it to app.json after creating your Flask application.
from flask import Flask
app = Flask(__name__)
app.json = CustomJSONProvider(app)
Alternatively, if you are subclassing Flask, you can set the json_provider_class attribute:
class MyFlask(Flask):
json_provider_class = CustomJSONProvider
app = MyFlask(__name__)
Step 5: Verify the Implementation
Now you can use jsonify to send User objects and request.get_json() to receive them.
from flask import jsonify, request
@app.route("/user", methods=["POST"])
def handle_user():
# Flask uses CustomJSONProvider.loads here
user = request.get_json()
print(f"Received: {user}") # Received: <User admin>
# Flask uses CustomJSONProvider.dumps (via response()) here
return jsonify(user)
# Example usage with a test client:
# client.post("/user", json={"_type": "User", "username": "admin", "email": "admin@example.com"})
Complete Working Result
Here is the full code combining all the steps:
import typing as t
from flask import Flask, jsonify, request
from flask.json.provider import DefaultJSONProvider
class User:
def __init__(self, username: str, email: str):
self.username = username
self.email = email
def __repr__(self):
return f"<User {self.username}>"
class CustomJSONProvider(DefaultJSONProvider):
def default(self, o: t.Any) -> t.Any:
if isinstance(o, User):
return {"_type": "User", "username": o.username, "email": o.email}
return super().default(o)
def object_hook(self, obj: dict[str, t.Any]) -> t.Any:
if obj.get("_type") == "User":
return User(username=obj["username"], email=obj["email"])
return obj
def loads(self, s: str | bytes, **kwargs: t.Any) -> t.Any:
kwargs.setdefault("object_hook", self.object_hook)
return super().loads(s, **kwargs)
app = Flask(__name__)
app.json = CustomJSONProvider(app)
@app.route("/user", methods=["POST"])
def handle_user():
user = request.get_json()
return jsonify(user)
if __name__ == "__main__":
app.run(debug=True)
Next Steps
- Alternative Libraries: If you want to use a faster library like
orjsonorujson, you can subclass the baseJSONProviderand implementdumpsandloadsusing those libraries. - Response Customization: Override the
responsemethod if you need to add custom headers or change the default mimetype for all JSON responses.