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.
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, , , , , )
(value.isoformat())
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:
datetime(...)creates a Pythondatetimeobject.created_at.isoformat()converts that object to the string"2012-08-08T21:46:24.862000+00:00".- The dictionary now contains only strings, which JSON supports.
jsonify(sample)encodes the dictionary into a JSON HTTP response.- The API client receives
somedateas a JSON string and can parse it as a date if needed.
The important detail is that conversion happens 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
datetimefields 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.
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:
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.
Comparisons
| Representation | Example | Advantages | Trade-offs |
|---|---|---|---|
| ISO 8601 string | "2024-06-10T14:30:00+00:00" | Readable, standard, preserves timezone offset | Client must parse a string |
| Unix timestamp in seconds | 1718029800 | Compact and simple for calculations | Timezone is implicit; precision may be lost |
| Unix timestamp in milliseconds | 1718029800123 | Common in JavaScript systems; includes milliseconds | Less human-readable; unit must be documented |
Python datetime object | datetime(...) | Excellent for Python date arithmetic | Not 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.
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
ObjectIdvalues also need conversion, usually withstr(). - 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.
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
Keep learning
Related questions
@staticmethod vs @classmethod in Python Explained
Learn the difference between @staticmethod and @classmethod in Python with clear examples, use cases, mistakes, and a mini project.
Add Rows to a Pandas DataFrame in Python
Learn how to add rows to a Pandas DataFrame, why repeated row appends are slow, and when to use loc, concat, or record lists.
Call a Function by Name in a Python Module
Learn how to call a function by name in a Python module using strings, getattr, and safe patterns for dynamic function dispatch.