TypeError: Object of type User is not JSON serializable
jsonify passes your data to Python’s json module. The encoder knows dicts, lists, strings, numbers, booleans and None, plus a few extra types Flask adds. Anything else reaches Flask’s default() hook, which raises this error and names the type it could not handle.
Traceback (most recent call last):
File "/app/venv/lib/python3.11/site-packages/flask/app.py", line 1473, in wsgi_app
response = self.full_dispatch_request()
File "/app/venv/lib/python3.11/site-packages/flask/app.py", line 882, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/app/venv/lib/python3.11/site-packages/flask/app.py", line 880, in full_dispatch_request
rv = self.dispatch_request()
File "/app/venv/lib/python3.11/site-packages/flask/app.py", line 865, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
File "/app/app/routes/users.py", line 14, in list_users
return jsonify(User.query.all())
File "/app/venv/lib/python3.11/site-packages/flask/json/__init__.py", line 170, in jsonify
return current_app.json.response(*args, **kwargs)
File "/app/venv/lib/python3.11/site-packages/flask/json/provider.py", line 214, in response
f"{self.dumps(obj, **dump_args)}\n", mimetype=self.mimetype
File "/app/venv/lib/python3.11/site-packages/flask/json/provider.py", line 179, in dumps
return json.dumps(obj, **kwargs)
File "/usr/lib/python3.11/json/__init__.py", line 238, in dumps
**kw).encode(obj)
File "/usr/lib/python3.11/json/encoder.py", line 200, in encode
chunks = self.iterencode(o, _one_shot=True)
File "/usr/lib/python3.11/json/encoder.py", line 258, in iterencode
return _iterencode(o, 0)
File "/app/venv/lib/python3.11/site-packages/flask/json/provider.py", line 121, in _default
raise TypeError(f"Object of type {type(o).__name__} is not JSON serializable")
TypeError: Object of type User is not JSON serializable
TLDR
Object of type User(or any model class name): you passed an ORM instance. Convert it to a dict first.Object of type set,bytes,Row: convert tolist,str, ordict(row._mapping).keys must be str, int, float, bool or None, not tuple: a dict key is the problem, not a value. Convert the keys.- A type you use in many places: subclass
DefaultJSONProviderand overridedefault(). - The last line of the trace names the type. Read that name before changing anything.
A SQLAlchemy model instance went into jsonify
The type name in the message is your model class, and the frame in your own code is a jsonify(...) call on Model.query.all(), db.session.get(...) or a single instance. Flask has no idea which columns you want exposed, so it does not guess.
@app.get("/users")
def list_users():
return jsonify(User.query.all()) # TypeError: Object of type User ...
The simplest fix is an explicit conversion:
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String, nullable=False)
created_at = db.Column(db.DateTime)
def to_dict(self):
return {"id": self.id, "email": self.email, "created_at": self.created_at}
@app.get("/users")
def list_users():
return jsonify([u.to_dict() for u in User.query.all()])
created_at can stay a datetime, because Flask’s provider formats it. Writing the fields out also stops password hashes and internal columns from leaking into the response, which __dict__-based shortcuts do not. Do not use vars(user): it includes _sa_instance_state, which fails with Object of type InstanceState is not JSON serializable.
If you have many models, a schema library (Marshmallow, Pydantic’s model_validate with from_attributes=True, or flask-marshmallow) saves you maintaining to_dict by hand.
A Row or Query object from a select
With SQLAlchemy 1.4 and 2.x, db.session.execute(select(...)) returns Row objects, and the error names Row. A Row is not a dict or list, so the encoder rejects it.
rows = db.session.execute(select(User.id, User.email)).all()
return jsonify([dict(r._mapping) for r in rows])
If you selected the full entity, use db.session.scalars(select(User)).all() to get model instances back and convert those as above. Passing a Query object without calling .all() fails the same way.
A set, bytes, or other type the encoder skips
json does not serialize set, frozenset, bytes or complex. Tuples become arrays, so those are fine as values.
return jsonify({"tags": {"a", "b"}}) # Object of type set is not JSON serializable
return jsonify({"tags": sorted({"a", "b"})}) # Fix: a list
return jsonify({"raw": b"abc"}) # Object of type bytes is not JSON serializable
return jsonify({"raw": b"abc".decode()}) # Fix: decode, or base64-encode binary data
Sort sets before returning them if clients compare responses, since set order is not stable.
A dictionary key is not a string
When the message is keys must be str, int, float, bool or None, not tuple, the failing item is a key. This happens when you build a dict like {(user_id, day): total} and pass it straight to jsonify. Values never trigger this message. Rebuild the dict with string keys, for example {f"{user_id}:{day}": total}, or return a list of objects.
A custom type you return in many places
For a type that shows up across the codebase, teach Flask about it once. On Flask 2.2 and later:
from flask import Flask
from flask.json.provider import DefaultJSONProvider
from money import Money # your own class
class AppJSONProvider(DefaultJSONProvider):
@staticmethod
def default(o):
if isinstance(o, Money):
return str(o)
if isinstance(o, set):
return sorted(o)
return DefaultJSONProvider.default(o)
app = Flask(__name__)
app.json = AppJSONProvider(app)
Call the parent default at the end so Flask’s built-in handling for datetime, Decimal, UUID and dataclasses keeps working, and so unknown types still raise a clear error. Setting app.json_provider_class = AppJSONProvider before creating the app (on a Flask subclass) works too; assigning it after Flask(...) is called does not rebuild app.json, which is why the snippet assigns an instance directly.
Older code that sets app.json_encoder = MyEncoder or subclasses flask.json.JSONEncoder was deprecated in Flask 2.2 and removed in 2.3. On a recent Flask it has no effect, so the original TypeError keeps coming back after you add it. Check the installed version with pip show flask.
The failing object is nested and the trace does not say where
The message names the type, not the key it was found under. When the payload is a large nested dict, a throwaway default shortens the search:
import json
print(json.dumps(payload, default=lambda o: f"<{type(o).__name__}>", indent=2))
The output shows "<Decimal128>" or "<ObjectId>" in place of each offending value, so you can see which keys hold them. Pasting the full traceback into the Debugly formatter separates your routes/users.py frame from the flask/json and json/encoder.py frames, which makes the one line that called jsonify easy to find.
The exception is raised while the response is being built, so Flask returns a 500 and a try/except around the database call in the view never sees it. See Flask 500 Internal Server Error for surfacing this traceback, and Flask SQLAlchemy session errors for problems loading the models.