PythonMastery
reference 3 min read · lesson 58 of 60 in Errors

TypeError: Object of type datetime is not JSON serializable

1 · The lesson

read

What this error means

json.dumps (or json.dump) met a value it doesn't know how to write as JSON. The standard encoder handles exactly these types: dict, list, tuple, str, int, float, bool and None. Anything else — a datetime, a Decimal, a set, a numpy number, an instance of your own class — and it gives up. The type name in the message tells you which value it choked on; it's datetime here, but you'll see Decimal, set, int64, ndarray or your own class name just as often.

When you see it

python
Traceback (most recent call last):
  File "main.py", line 4, in <module>
    json.dumps(order)
    ~~~~~~~~~~^^^^^^^
  File ".../json/__init__.py", line 235, in dumps
    return _default_encoder.encode(obj)
           ~~~~~~~~~~~~~~~~~~~~~~~^^^^^
  File ".../json/encoder.py", line 202, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File ".../json/encoder.py", line 263, in iterencode
    return _iterencode(o, 0)
  File ".../json/encoder.py", line 182, in default
    raise TypeError(f'Object of type {o.__class__.__name__} '
                    f'is not JSON serializable')
TypeError: Object of type datetime is not JSON serializable

The same error with other types, last line only:

python
TypeError: Object of type Decimal is not JSON serializable
TypeError: Object of type set is not JSON serializable
TypeError: Object of type int64 is not JSON serializable
TypeError: Object of type Order is not JSON serializable

Notice the traceback doesn't say which key held the bad value. In a big nested payload you have to hunt for it — print the dict and look for anything that isn't plain text, a number or a list.

Why it happens

JSON has no date type, no decimal type and no set type, so there's no single "correct" way to write them. Python refuses to guess. A date could reasonably be "2026-10-09T14:30:00", a Unix timestamp, or "09/10/2026", and picking the wrong one silently is worse than failing loudly.

python
import json
from datetime import datetime

order = {"id": 1042, "customer": "Ada", "placed_at": datetime(2026, 10, 9, 14, 30)}
json.dumps(order)           # TypeError: Object of type datetime is not JSON serializable

The usual suspects:

  • datetime / date — from a database row, datetime.now(), or a parsed timestamp.
  • Decimal — money columns from PostgreSQL, MySQL or SQLAlchemy come back as Decimal.
  • set — you deduplicated tags with set(...) and put the result straight in the payload.
  • numpy and pandas numbers — df["qty"].sum() returns numpy.int64, not int. Oddly, numpy.float64 does serialise, because it subclasses Python's float; int64 doesn't subclass int.
  • Your own classes — json has no idea what an Order or User object should look like.

How to fix it

Convert at the source when there's only one or two fields. It's the most explicit option and the output format is exactly what you chose:

python
import json
from datetime import datetime

placed_at = datetime(2026, 10, 9, 14, 30)
order = {"id": 1042, "customer": "Ada", "placed_at": placed_at.isoformat()}
print(json.dumps(order))
# {"id": 1042, "customer": "Ada", "placed_at": "2026-10-09T14:30:00"}

ISO 8601 (isoformat()) is the format to pick for dates in JSON. Every language parses it, and it sorts correctly as a string.

default=str for logs and debugging. default is called for every value the encoder doesn't recognise. str is the lazy catch-all:

python
import json
from datetime import datetime
from decimal import Decimal

invoice = {"number": "INV-2041", "total": Decimal("312.40"), "issued": datetime(2026, 10, 9, 9, 0)}
print(json.dumps(invoice, default=str))
# {"number": "INV-2041", "total": "312.40", "issued": "2026-10-09 09:00:00"}

Fine for a log line. For an API I wouldn't ship it: str(datetime) puts a space where ISO 8601 wants a T, and anything unknown gets stringified without complaint — including objects you never meant to send.

A small default function for real payloads. Handle the types you expect and keep raising for the rest, so a stray object still fails loudly:

python
import json
from datetime import date, datetime
from decimal import Decimal

def to_json(value):
    if isinstance(value, (datetime, date)):
        return value.isoformat()
    if isinstance(value, Decimal):
        return str(value)           # keep every digit; float(value) can lose pennies
    if isinstance(value, set):
        return sorted(value)
    raise TypeError(f"Object of type {type(value).__name__} is not JSON serializable")

order = {
    "id": 1042,
    "customer": "Grace",
    "placed_at": datetime(2026, 10, 9, 14, 30),
    "total": Decimal("89.99"),
    "tags": {"priority", "gift"},
}
print(json.dumps(order, default=to_json, indent=2))

Sending money as a string surprises people, but it's the safe choice. float(Decimal("0.10")) + float(Decimal("0.20")) is 0.30000000000000004, and the client can parse a string back into an exact decimal.

numpy values: call .item() or .tolist(). .item() turns a numpy scalar into the matching Python type; .tolist() does the same for a whole array:

python
import json
import numpy as np

units = np.array([12, 40, 3])
report = {"total_units": units.sum().item(), "per_store": units.tolist()}
print(json.dumps(report))   # {"total_units": 55, "per_store": [12, 40, 3]}

If the data is already in a DataFrame, skip json entirely: df.to_json(orient="records", date_format="iso") knows about numpy types and timestamps.

Your own classes: a dataclass and asdict. dataclasses.asdict converts the object (and nested dataclasses) to plain dicts. It leaves field values alone, so a datetime field still needs the default:

python
import json
from dataclasses import dataclass, asdict
from datetime import date

@dataclass
class Order:
    id: int
    customer: str
    placed_on: date

order = Order(1042, "Hedy", date(2026, 10, 9))
print(json.dumps(asdict(order), default=lambda v: v.isoformat()))
# {"id": 1042, "customer": "Hedy", "placed_on": "2026-10-09"}

For API models with validation, pydantic's model_dump_json() handles dates, decimals and nested models out of the box — worth it if you're already in FastAPI land, overkill for a script.

When you'd actually see this in real code

  • Returning a database row from a Flask view: the created_at column is a datetime, and jsonify or json.dumps refuses it.
  • Writing a sales summary to JSON after df.groupby(...).sum(): every number is numpy.int64.
  • Caching a result in Redis with json.dumps, where the result contains a set of user IDs.
  • Logging an object with json.dumps(self.__dict__) and one attribute is a Decimal price or a nested object.

See Also

Found this useful? Share it with a friend who’s learning Python.