TypeError: Object of type datetime is not JSON serializable
Python’s json module knows how to write dicts, lists, strings, numbers, True, False, and None. When it reaches any other kind of value, it raises this error and puts the type in the message. datetime is the one people hit most, often from a database row, but Decimal, UUID, sets, and NumPy numbers fail the same way. To fix it, you tell json.dumps how to convert those values.
Quick fix
- Check which type the message names. That’s the value
json.dumpsdidn’t know how to write. - For a quick script or a log line,
json.dumps(data, default=str)turns any value it doesn’t know into a string. - If other code will read the JSON, pass a
defaultfunction like the one below instead, so you decide how dates and the other values are written.
Why the error keeps coming back with a different type
Here’s an order record with four values that JSON can’t hold directly:
import json, uuid
from datetime import datetime, timezone
from decimal import Decimal
order = {
"id": uuid.UUID("3f2b8c1e-9d4a-4e7b-8a61-2c5d9e0f7b13"),
"created": datetime(2026, 10, 26, 9, 30, tzinfo=timezone.utc),
"total": Decimal("19.99"),
"tags": {"gift"},
}
json.dumps(order)
# TypeError: Object of type UUID is not JSON serializableThe error only mentions UUID, because json.dumps stops at the first value it can’t handle. Convert the UUID, run it again, and you’ll get the same error for the datetime, then the Decimal, then the set. It’s less work to handle all of them in one place, which is what the default argument is for.
The quick fix: default=str
json.dumps calls the function you pass as default whenever it reaches a value it can’t write, and writes whatever that function returns instead. Passing str turns each of those values into a string:
json.dumps(order, default=str)
# {"id": "3f2b8c1e-9d4a-4e7b-8a61-2c5d9e0f7b13",
# "created": "2026-10-26 09:30:00+00:00",
# "total": "19.99", "tags": "{'gift'}"}For a log line, that’s usually good enough. If another program reads the result, though, two of these values cause trouble. The date has a space between the date and the time where ISO 8601 uses a T, and some parsers won’t accept it. Go’s time.Time is one of them. And the set has become the string "{'gift'}", which is just how Python prints it, not a list.
A default function for the usual types
With your own function, you can pick the right format for each type:
import base64, dataclasses, datetime, decimal, uuid
def to_json(value):
if isinstance(value, (datetime.date, datetime.time)):
return value.isoformat()
if isinstance(value, (decimal.Decimal, uuid.UUID)):
return str(value)
if isinstance(value, (set, frozenset)):
return list(value)
if isinstance(value, bytes):
return base64.b64encode(value).decode("ascii")
if dataclasses.is_dataclass(value):
return dataclasses.asdict(value)
if hasattr(value, "tolist"): # NumPy numbers and arrays
return value.tolist()
raise TypeError(f"Object of type {type(value).__name__} is not JSON serializable")
print(json.dumps(order, default=to_json, indent=2)){
"id": "3f2b8c1e-9d4a-4e7b-8a61-2c5d9e0f7b13",
"created": "2026-10-26T09:30:00+00:00",
"total": "19.99",
"tags": [
"gift"
]
}The last line of the function matters. If a type comes along that it doesn’t handle, you get the same TypeError as before, instead of a value quietly written in the wrong format. If you’d rather use a class, the same code can go in the default method of a json.JSONEncoder subclass, which you pass with cls=.
What to turn each type into
| Type in the message | Convert with | Notes |
|---|---|---|
datetime, date, time | value.isoformat() | Gives 2026-10-26T09:30:00+00:00. If the value has a time zone, it’s kept. |
Decimal | str(value) | Keeps the exact amount. float(value) also works if the reader expects a number, but it can lose digits. |
UUID | str(value) | Gives the usual 36-character form, like 3f2b8c1e-9d4a-… |
set, frozenset | list(value) | Sets have no order, so use sorted(value) if you want the output to be the same every time. |
bytes | base64.b64encode(value).decode("ascii") | JSON can’t hold raw bytes, so they have to be written as text. Base64 is the usual way. |
A dataclass, like User | dataclasses.asdict(value) | Gives a dict of the fields, including any nested dataclasses. |
int64, float32, ndarray, bool | value.tolist() | These come from NumPy. If the message says bool, it means numpy.bool, which json can’t write even though Python’s own bool is fine. |
Timestamp | value.isoformat() | A pandas date. For a whole DataFrame, it’s easier to let pandas write the JSON, as shown below. |
Your own class | vars(value) | Gives a dict of the object’s attributes, which works if each of them can be written. Otherwise, give the class a to_dict() method and call that. |
Reading the values back
JSON has no date or decimal type, so when you load the JSON again with json.loads, those values come back as strings. Convert them in the code that uses them:
data = json.loads(text)
created = datetime.fromisoformat(data["created"])
total = Decimal(data["total"])In Django, FastAPI, and pandas
Django
JsonResponse already encodes with DjangoJSONEncoder, which knows about dates, times, Decimal, and UUID. If you call json.dumps yourself, pass cls=DjangoJSONEncoder to get the same behavior. It doesn’t handle sets, so turn those into lists first.
FastAPI and Pydantic
When you return a Pydantic model from a FastAPI route, it’s converted for you. The error tends to show up when you pass a model’s data to json.dumps yourself, for example to cache it or put it on a queue. Call model.model_dump(mode="json") first, which turns dates and decimals into strings. For values that aren’t Pydantic models, FastAPI’s jsonable_encoder does the same job.
pandas
json.dumps(df.to_dict("records")) fails on the first Timestamp or NumPy number in the frame. DataFrame.to_json handles both, so let pandas write the JSON:
df.to_json(orient="records", date_format="iso")
# [{"when":"2026-10-26T09:30:00.000","n":1}]The same error elsewhere
| Python, circular reference | ValueError: Circular reference detected |
|---|---|
| JavaScript, BigInt | TypeError: Do not know how to serialize a BigInt |
| Go | json: unsupported type: chan int |
| PHP, json_last_error_msg() | Type is not supported |