Question

Serialize Python datetime Objects to JSON and Flask jsonify

pythonjson

Question

A dictionary returned from MongoDB contains a Python datetime.datetime value. Calling Flask's jsonify() with that dictionary raises a serialization error.

sample = {}
sample["title"] = "String"
sample["somedate"] = somedatetimehere

jsonify(sample)

The error is:

TypeError: datetime.datetime(2012, 8, 8, 21, 46, 24, 862000) is not JSON serializable

Printing str(sample["somedate"]) produces 2012-08-08 21:46:24.862000. How can this datetime value be safely included in a JSON response?

Short Answer

JSON has no built-in date or datetime type, while MongoDB drivers commonly return date fields as Python datetime objects. You will learn to convert datetimes into a stable JSON-friendly representation—usually an ISO 8601 string—before returning them from a Flask API.

Concept

JSON supports only these value types:

  • object
  • array
  • string
  • number
  • boolean
  • null

A Python datetime.datetime object is not one of those types. It contains behavior and metadata such as formatting methods, timezone information, and date arithmetic. A JSON encoder cannot guess how you want that object represented, so it raises a TypeError.

The usual solution is to choose a JSON-compatible representation explicitly. For API dates, an ISO 8601 string is usually the best choice:

"2012-08-08T21:46:24.862000+00:00"

This format is readable, widely supported by JavaScript and other clients, and preserves the date, time, fractional seconds, and timezone when the datetime is timezone-aware.

MongoDB date fields are commonly decoded into Python datetime objects by PyMongo. That is useful inside Python, but the object must still be converted at the API boundary before it becomes JSON.

Mental Model

Think of JSON as a shipping box with a strict list of accepted item types. Python's datetime is a clock with moving parts; it does not fit directly in the box.

Before shipping it, convert the clock into a label that every recipient can read:

Python datetime object  ->  "2024-06-10T14:30:00+00:00" JSON string

Your server can convert that label back into a datetime later if it needs to perform date calculations.

Take Quiz

Syntax and Examples

Convert a single datetime with isoformat() before passing the data to jsonify().

from datetime import datetime, timezone
from flask import jsonify

sample = {
    "title": "String",
    "somedate": datetime(2012, 8, 8, 21, 46, 24, 862000, tzinfo=timezone.utc),
}

response_data = {
    **sample,
    "somedate": sample["somedate"].isoformat(),
}

return jsonify(response_data)

The resulting JSON contains a string:

{
  "somedate": "2012-08-08T21:46:24.862000+00:00",
  "title": "String"
}

For a datetime without timezone information, isoformat() still works:

value = datetime(2012, 8, 8, 21, 46, 24, 862000)
print(value.isoformat())
# 2012-08-08T21:46:24.862000

However, that output does not say whether the time is UTC, server-local time, or another timezone. Prefer timezone-aware datetimes for data exchanged between systems.

Step by Step Execution

Consider this Flask route:

from datetime import datetime, timezone
from flask import Flask, jsonify

app = Flask(__name__)

@app.get("/sample")
def get_sample():
    created_at = datetime(2012, 8, 8, 21, 46, 24, 862000, tzinfo=timezone.utc)

    sample = {
        "title": "String",
        "somedate": created_at.isoformat(),
    }

    return jsonify(sample)

Execution proceeds as follows:

  1. datetime(...) creates a Python datetime object.
  2. created_at.isoformat() converts that object to the string "2012-08-08T21:46:24.862000+00:00".
  3. The dictionary now contains only strings, which JSON supports.
  4. jsonify(sample) encodes the dictionary into a JSON HTTP response.
  5. The API client receives somedate as a JSON string and can parse it as a date if needed.

The important detail is that conversion happens before JSON encoding.

Real World Use Cases

Datetime serialization appears whenever an application sends stored or calculated timestamps outside Python:

  • REST APIs: Return created_at, updated_at, booking times, or event timestamps.
  • MongoDB-backed services: Convert PyMongo datetime fields in documents before returning them from an endpoint.
  • Audit logs: Send the time a user signed in, changed a setting, or deleted a record.
  • Background jobs: Produce JSON messages containing a job start time or retry deadline.
  • Reports and exports: Include timestamps in JSON files consumed by another service.
  • Frontend applications: Give a browser an ISO 8601 timestamp that JavaScript can parse with new Date(value).

For data shared across timezones, store and exchange timestamps in UTC whenever possible.

Take Quiz

Real Codebase Usage

In real applications, avoid scattering .isoformat() calls throughout every route. Put serialization at a clear boundary, such as a response mapper or a custom JSON provider.

A small mapper is easy to test and makes the API contract explicit:

from datetime import datetime


def serialize_document(document: dict) -> dict:
    result = dict(document)  # Do not mutate the database result.

    if isinstance(result.get("somedate"), datetime):
        result["somedate"] = result["somedate"].isoformat()

    return result

Use it in a route:

@app.get("/samples/<sample_id>")
def get_sample(sample_id):
    document = collection.find_one({"_id": sample_id})
    if document is None:
        return {"error": "Sample not found"}, 404

    return jsonify(serialize_document(document))

For nested dictionaries and lists, use a reusable encoder:

from datetime import date, datetime


def to_json_value(value):
    if isinstance(value, (datetime, date)):
        return value.isoformat()
    if isinstance(value, dict):
        return {key: to_json_value(item) for key, item in value.items()}
    if isinstance(value, list):
        return [to_json_value(item) for item in value]
    return value

This pattern is particularly useful for MongoDB documents, which may contain nested data. MongoDB documents also often include an _id value of type ObjectId; that type needs its own conversion, commonly str(document["_id"]), because it is not standard JSON either.

Common Mistakes

Assuming str() is the best API format

This works technically:

sample["somedate"] = str(sample["somedate"])

But its output uses a space:

2012-08-08 21:46:24.862000

isoformat() communicates the format more clearly and produces the conventional T separator:

sample["somedate"] = sample["somedate"].isoformat()

Passing the datetime directly

This fails with encoders that do not provide datetime handling:

return jsonify({"somedate": datetime.now()})

Convert the value first, or configure one centralized serializer.

Adding Z to a naive datetime

This is misleading:

created_at = datetime.now()
text = created_at.isoformat() + "Z"  # Incorrect unless created_at is UTC.

Z means UTC. Use an aware UTC datetime instead:

from datetime import datetime, timezone

created_at = datetime.now(timezone.utc)
text = created_at.isoformat().replace("+00:00", "Z")

Mutating a MongoDB document accidentally

Changing a document in place can be surprising if the same object is later reused:

# Avoid when the original document is still needed.
document["somedate"] = document["somedate"].isoformat()

Make a copy first:

response = dict(document)
response["somedate"] = response["somedate"].isoformat()

Forgetting other non-JSON MongoDB types

After fixing a datetime, a document may fail on _id because ObjectId is also not a standard JSON value. Convert every non-JSON type deliberately rather than assuming MongoDB documents can be sent directly.

Comparisons

RepresentationExampleAdvantagesTrade-offs
ISO 8601 string"2024-06-10T14:30:00+00:00"Readable, standard, preserves timezone offsetClient must parse a string
Unix timestamp in seconds1718029800Compact and simple for calculationsTimezone is implicit; precision may be lost
Unix timestamp in milliseconds1718029800123Common in JavaScript systems; includes millisecondsLess human-readable; unit must be documented
Python datetime objectdatetime(...)Excellent for Python date arithmeticNot a JSON value

For public JSON APIs, ISO 8601 is often the clearest default. Use timestamps when your API already has a documented epoch-and-unit convention.

Python methodPurpose
value.isoformat()Convert a date or datetime to a standard text representation
str(value)Produce a human-readable string, but with a less explicit API format
value.timestamp()Convert an aware datetime to seconds since the Unix epoch
datetime.fromisoformat(text)Parse many ISO 8601-style strings back into a Python datetime
Take Quiz

Cheat Sheet

from datetime import datetime, timezone

# Create an aware UTC datetime
now = datetime.now(timezone.utc)

# Best general JSON representation
json_value = now.isoformat()
# Example: "2024-06-10T14:30:00+00:00"

# Use in a Flask response
return jsonify({"createdAt": now.isoformat()})
  • JSON does not define a datetime type.
  • Convert datetimes to strings or numbers before JSON serialization.
  • Prefer ISO 8601 strings for API payloads.
  • Prefer timezone-aware UTC datetimes for cross-system data.
  • Do not claim a naive datetime is UTC by appending Z.
  • MongoDB ObjectId values also need conversion, usually with str().
  • For nested documents, recursively serialize dictionaries and lists.

FAQ

Why is datetime.datetime not JSON serializable in Python?

The JSON standard supports strings, numbers, booleans, null, arrays, and objects, but not date objects. Python's datetime must be converted to a supported value first.

What is the best way to serialize a Python datetime to JSON?

For most APIs, use datetime_value.isoformat(). It produces a standard, readable ISO 8601 string.

Can Flask jsonify() serialize datetimes automatically?

Behavior can vary with Flask version and JSON-provider configuration. Explicitly converting dates in your response layer gives your API a stable, documented format.

Should I use str(datetime_value) or datetime_value.isoformat()?

Prefer isoformat() for API data. It uses a well-known date-time convention and can include the timezone offset.

How do I make a datetime UTC-aware in Python?

Create it with datetime.now(timezone.utc) or convert an existing aware datetime with .astimezone(timezone.utc).

What does the Z suffix mean in a date string?

Z means UTC. Only use it for a value that actually represents UTC time.

Why does MongoDB data still fail after I convert the date?

MongoDB documents often contain an ObjectId in _id. ObjectId is not a JSON type either, so serialize it separately, commonly with str().

Can a client turn the ISO string back into a date?

Yes. JavaScript clients can generally parse an ISO 8601 timestamp with new Date(isoString). Other languages provide similar parsing tools.

Take Quiz

Mini Project

Description

Build a small Flask endpoint that returns task records similar to documents retrieved from MongoDB. Each task has a Python datetime value, and the endpoint converts it into an API-friendly ISO 8601 string.

Goal

Return a JSON task list containing serialized UTC timestamps without datetime serialization errors.

Requirements

Requirement 1 Requirement 2 Requirement 3

Take Quiz

Keep learning

Related questions

View more