Python
JSON
datetime
serialization
error-handling

How can I overcome datetime.datetime not JSON serializable?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Python's built-in json module handles only basic JSON data types such as strings, numbers, booleans, lists, and dictionaries. A datetime.datetime object is a Python-specific type, so json.dumps raises TypeError: Object of type datetime is not JSON serializable when it encounters one.

To fix that, convert datetime values into a JSON-friendly representation before encoding them. In practice, the best options are ISO 8601 strings, a custom default function, or a reusable JSONEncoder subclass.

Why The Error Happens

JSON itself has no official datetime type. That means every application must decide how dates and times should be represented. Python's standard library intentionally does not guess for you because one system may want ISO strings while another expects Unix timestamps.

This fails because finished_at is a datetime object:

python
1import json
2from datetime import datetime
3
4payload = {
5    "event": "backup_finished",
6    "finished_at": datetime.now(),
7}
8
9print(json.dumps(payload))

If you run that code, json.dumps stops at the unsupported value and throws the serialization error.

Convert Datetimes To ISO 8601 Strings

The simplest fix is to call isoformat() when building the payload. ISO 8601 is readable, widely accepted by APIs, and easy to parse later.

python
1import json
2from datetime import datetime, timezone
3
4payload = {
5    "event": "backup_finished",
6    "finished_at": datetime.now(timezone.utc).isoformat(),
7}
8
9json_text = json.dumps(payload)
10print(json_text)

This is usually the best option when you know exactly which fields are datetimes. It keeps the conversion explicit and makes the payload format obvious to future readers.

If an external system requires a custom shape, strftime() also works:

python
1from datetime import datetime
2
3created_at = datetime(2026, 3, 7, 14, 30, 0)
4formatted = created_at.strftime("%Y-%m-%d %H:%M:%S")
5print(formatted)

Use custom formatting only when a contract requires it. Otherwise, ISO 8601 is the safer default.

Use default= For Centralized Conversion

When datetime values appear throughout an application, converting each field manually becomes repetitive. json.dumps accepts a default callback that can serialize unsupported objects in one place.

python
1import json
2from datetime import date, datetime, timezone
3
4def json_default(value):
5    if isinstance(value, (datetime, date)):
6        return value.isoformat()
7    raise TypeError(f"Unsupported type: {type(value).__name__}")
8
9payload = {
10    "created_at": datetime.now(timezone.utc),
11    "due_date": date(2026, 4, 1),
12}
13
14print(json.dumps(payload, default=json_default, indent=2))

This pattern is useful when payloads are nested or shared across modules. It also fails loudly for unknown types instead of silently converting everything with str().

Create A Custom JSON Encoder

If you need the same behavior everywhere, define a custom encoder class and pass it with cls=.

python
1import json
2from datetime import date, datetime
3
4class DateTimeEncoder(json.JSONEncoder):
5    def default(self, value):
6        if isinstance(value, (datetime, date)):
7            return value.isoformat()
8        return super().default(value)
9
10payload = {
11    "name": "quarterly-report",
12    "generated_at": datetime(2026, 3, 7, 10, 15, 0),
13}
14
15print(json.dumps(payload, cls=DateTimeEncoder))

This is a good fit for shared libraries, API clients, or framework code where serialization rules should stay consistent across the project.

Strings Versus Timestamps

Some systems prefer Unix timestamps instead of strings. That is also valid, but it is less self-explanatory and easier to misuse when timezone handling is unclear.

python
1import json
2from datetime import datetime, timezone
3
4dt = datetime(2026, 3, 7, 15, 45, tzinfo=timezone.utc)
5payload = {
6    "finished_at": int(dt.timestamp())
7}
8
9print(json.dumps(payload))

Timestamps are compact and common in analytics pipelines, but ISO strings are usually easier to debug and safer for public APIs.

Common Pitfalls

One common mistake is serializing naive datetimes that have no timezone. The value may look correct, but downstream systems cannot tell whether it represents local time or UTC. Prefer timezone-aware values, often in UTC, before calling isoformat().

Another mistake is using default=str as a quick fix. That avoids the immediate exception, but it converts every unsupported object with whatever str() returns, which can hide bugs and make payloads inconsistent.

It is also easy to forget the decoding side. Once you serialize a datetime, the receiver gets a string or number, not a Python datetime object. If your code later needs datetime arithmetic, parse the value back explicitly.

Summary

  • 'datetime.datetime is not a native JSON type, so json.dumps cannot encode it automatically.'
  • 'isoformat() is the simplest and most portable solution for most applications.'
  • Use default= or a custom JSONEncoder when datetime values appear in many payloads.
  • Prefer timezone-aware datetimes so serialized values are unambiguous.
  • Avoid broad shortcuts such as default=str unless you accept the loss of type control.

Course illustration
Course illustration

All Rights Reserved.