Handling Non-Serializable Types like Datetimes
๐ท๏ธ Structured Data Formats: JSON, YAML, and CSV / JSON In-Depth
When working with JSON in Python, you'll quickly discover that not everything can be automatically converted to JSON format. Python's datetime objects are a classic example of a non-serializable type โ they cannot be directly converted to JSON because JSON has no native date or time representation. This section explains why this happens and how to handle it cleanly.
โ๏ธ Why Datetimes Are Not JSON-Serializable
- JSON supports only a limited set of data types: strings, numbers, booleans, null, arrays, and objects (dictionaries).
- Python's datetime.datetime, datetime.date, and datetime.time objects are not part of this set.
- When you try to serialize a datetime object using json.dumps(), Python raises a TypeError with a message like: "Object of type datetime is not JSON serializable".
๐ ๏ธ Two Common Approaches to Handle Datetimes
| Approach | Description | Best For |
|---|---|---|
| Custom Serializer Function | Write a function that converts datetime objects to a string format (e.g., ISO 8601) and pass it to json.dumps() via the default parameter | Simple, one-off conversions |
| Custom JSON Encoder Class | Create a subclass of json.JSONEncoder that overrides the default() method | Reusable across multiple files or projects |
๐ต๏ธ Approach 1: Custom Serializer Function
- Define a function that checks if an object is a datetime instance and converts it to a string.
- Use the isoformat() method to produce a standardized, human-readable string like "2025-03-15T14:30:00".
- Pass this function to json.dumps() using the default parameter.
Example structure:
- Create a function called serialize_datetime that accepts a single argument obj.
- Inside the function, check isinstance(obj, datetime.datetime).
- If true, return obj.isoformat().
- If false, raise a TypeError with a message like "Type {type(obj)} is not serializable".
- When calling json.dumps(), add default=serialize_datetime as a parameter.
Result: Your JSON output will contain datetime values as ISO 8601 strings, which are widely supported by other systems and languages.
๐ Approach 2: Custom JSON Encoder Class
- Create a class that inherits from json.JSONEncoder.
- Override the default() method with the same logic as the custom function above.
- Use this encoder class by passing cls=YourEncoderClassName to json.dumps().
Example structure:
- Define a class called DateTimeEncoder that extends json.JSONEncoder.
- Inside the class, define a method called default(self, obj).
- Use the same isinstance check and isoformat() conversion.
- For non-datetime objects, call super().default(obj) to let the parent class handle them.
- When serializing, use json.dumps(data, cls=DateTimeEncoder).
Result: This approach is cleaner for larger projects where you serialize datetime objects in many places. You only define the encoder once and reuse it everywhere.
๐ Deserializing Datetime Strings Back to Python Objects
- When reading JSON back into Python, datetime strings remain as plain strings.
- To convert them back to datetime objects, use a custom object_hook function with json.loads().
- The object_hook function receives each dictionary and can transform string values that match a datetime pattern.
Example structure:
- Define a function called parse_datetime that accepts a dictionary dct.
- Loop through the dictionary keys and values.
- For each value, try to parse it using datetime.fromisoformat().
- If successful, replace the string with the datetime object.
- Return the modified dictionary.
- Pass this function to json.loads() using object_hook=parse_datetime.
Result: Your loaded data will contain actual Python datetime objects instead of plain strings, making it easier to work with date arithmetic and formatting.
โ Best Practices for Engineers
- Always use ISO 8601 format for datetime serialization โ it is the standard for data interchange and is supported by most programming languages and databases.
- Store datetime values as UTC to avoid timezone confusion when sharing data across systems.
- Document your datetime format clearly in API documentation or data contracts so consumers know how to parse the values.
- Consider using third-party libraries like pytz or dateutil for advanced timezone handling if your project requires it.
- Test your serialization and deserialization logic with edge cases like midnight, leap years, and daylight saving time transitions.
๐ง Summary
- Python datetime objects are not natively JSON-serializable because JSON lacks date/time types.
- Use a custom serializer function or a custom JSONEncoder class to convert datetimes to ISO 8601 strings.
- Use an object_hook function to convert those strings back into datetime objects when loading JSON.
- Stick to ISO 8601 and UTC for consistency and interoperability across systems.
Handling non-serializable types like datetimes is a common task when building data pipelines, APIs, or configuration systems. Mastering this pattern ensures your JSON data remains clean, portable, and easy to work with across different tools and languages.
This section shows how to handle Python objects like datetimes that JSON cannot natively convert to strings.
๐งช Example 1: The Default JSON Serialization Error
This example demonstrates what happens when you try to serialize a datetime object without any special handling.
import json
from datetime import datetime
current_time = datetime.now()
data = {
"event": "deployment",
"timestamp": current_time
}
result = json.dumps(data)
๐ค Output: TypeError: Object of type datetime is not JSON serializable
๐งช Example 2: Manual String Conversion Before Serialization
This example shows how to manually convert a datetime to a string before passing it to JSON.
import json
from datetime import datetime
current_time = datetime.now()
data = {
"event": "deployment",
"timestamp": str(current_time)
}
result = json.dumps(data)
print(result)
๐ค Output: {"event": "deployment", "timestamp": "2025-04-08 14:30:00.123456"}
๐งช Example 3: Using a Custom Serialization Function
This example shows how to write a custom function that converts non-serializable types to strings.
import json
from datetime import datetime
def custom_serializer(obj):
if isinstance(obj, datetime):
return obj.isoformat()
raise TypeError(f"Type {type(obj)} not serializable")
current_time = datetime.now()
data = {
"event": "deployment",
"timestamp": current_time
}
result = json.dumps(data, default=custom_serializer)
print(result)
๐ค Output: {"event": "deployment", "timestamp": "2025-04-08T14:30:00.123456"}
๐งช Example 4: Using a Custom JSON Encoder Class
This example shows how to create a reusable JSON encoder class for handling datetimes.
import json
from datetime import datetime
class DateTimeEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime):
return obj.isoformat()
return super().default(obj)
current_time = datetime.now()
data = {
"event": "deployment",
"timestamp": current_time,
"status": "completed"
}
result = json.dumps(data, cls=DateTimeEncoder)
print(result)
๐ค Output: {"event": "deployment", "timestamp": "2025-04-08T14:30:00.123456", "status": "completed"}
๐งช Example 5: Handling Multiple Non-Serializable Types
This example shows how to handle both datetimes and other non-serializable types like sets.
import json
from datetime import datetime
class CustomEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime):
return obj.isoformat()
if isinstance(obj, set):
return list(obj)
return super().default(obj)
current_time = datetime.now()
tags = {"production", "critical", "v3"}
data = {
"event": "deployment",
"timestamp": current_time,
"tags": tags
}
result = json.dumps(data, cls=CustomEncoder)
print(result)
๐ค Output: {"event": "deployment", "timestamp": "2025-04-08T14:30:00.123456", "tags": ["production", "critical", "v3"]}
๐ Comparison Table: Handling Non-Serializable Types
| Method | Code Complexity | Reusability | Handles Multiple Types |
|---|---|---|---|
| Manual string conversion | Low | Low | No |
| Custom serializer function | Medium | Medium | Yes |
| Custom JSON encoder class | Medium | High | Yes |
Recommendation: Use a custom JSON encoder class when you need to handle datetimes across multiple files or projects.
When working with JSON in Python, you'll quickly discover that not everything can be automatically converted to JSON format. Python's datetime objects are a classic example of a non-serializable type โ they cannot be directly converted to JSON because JSON has no native date or time representation. This section explains why this happens and how to handle it cleanly.
โ๏ธ Why Datetimes Are Not JSON-Serializable
- JSON supports only a limited set of data types: strings, numbers, booleans, null, arrays, and objects (dictionaries).
- Python's datetime.datetime, datetime.date, and datetime.time objects are not part of this set.
- When you try to serialize a datetime object using json.dumps(), Python raises a TypeError with a message like: "Object of type datetime is not JSON serializable".
๐ ๏ธ Two Common Approaches to Handle Datetimes
| Approach | Description | Best For |
|---|---|---|
| Custom Serializer Function | Write a function that converts datetime objects to a string format (e.g., ISO 8601) and pass it to json.dumps() via the default parameter | Simple, one-off conversions |
| Custom JSON Encoder Class | Create a subclass of json.JSONEncoder that overrides the default() method | Reusable across multiple files or projects |
๐ต๏ธ Approach 1: Custom Serializer Function
- Define a function that checks if an object is a datetime instance and converts it to a string.
- Use the isoformat() method to produce a standardized, human-readable string like "2025-03-15T14:30:00".
- Pass this function to json.dumps() using the default parameter.
Example structure:
- Create a function called serialize_datetime that accepts a single argument obj.
- Inside the function, check isinstance(obj, datetime.datetime).
- If true, return obj.isoformat().
- If false, raise a TypeError with a message like "Type {type(obj)} is not serializable".
- When calling json.dumps(), add default=serialize_datetime as a parameter.
Result: Your JSON output will contain datetime values as ISO 8601 strings, which are widely supported by other systems and languages.
๐ Approach 2: Custom JSON Encoder Class
- Create a class that inherits from json.JSONEncoder.
- Override the default() method with the same logic as the custom function above.
- Use this encoder class by passing cls=YourEncoderClassName to json.dumps().
Example structure:
- Define a class called DateTimeEncoder that extends json.JSONEncoder.
- Inside the class, define a method called default(self, obj).
- Use the same isinstance check and isoformat() conversion.
- For non-datetime objects, call super().default(obj) to let the parent class handle them.
- When serializing, use json.dumps(data, cls=DateTimeEncoder).
Result: This approach is cleaner for larger projects where you serialize datetime objects in many places. You only define the encoder once and reuse it everywhere.
๐ Deserializing Datetime Strings Back to Python Objects
- When reading JSON back into Python, datetime strings remain as plain strings.
- To convert them back to datetime objects, use a custom object_hook function with json.loads().
- The object_hook function receives each dictionary and can transform string values that match a datetime pattern.
Example structure:
- Define a function called parse_datetime that accepts a dictionary dct.
- Loop through the dictionary keys and values.
- For each value, try to parse it using datetime.fromisoformat().
- If successful, replace the string with the datetime object.
- Return the modified dictionary.
- Pass this function to json.loads() using object_hook=parse_datetime.
Result: Your loaded data will contain actual Python datetime objects instead of plain strings, making it easier to work with date arithmetic and formatting.
โ Best Practices for Engineers
- Always use ISO 8601 format for datetime serialization โ it is the standard for data interchange and is supported by most programming languages and databases.
- Store datetime values as UTC to avoid timezone confusion when sharing data across systems.
- Document your datetime format clearly in API documentation or data contracts so consumers know how to parse the values.
- Consider using third-party libraries like pytz or dateutil for advanced timezone handling if your project requires it.
- Test your serialization and deserialization logic with edge cases like midnight, leap years, and daylight saving time transitions.
๐ง Summary
- Python datetime objects are not natively JSON-serializable because JSON lacks date/time types.
- Use a custom serializer function or a custom JSONEncoder class to convert datetimes to ISO 8601 strings.
- Use an object_hook function to convert those strings back into datetime objects when loading JSON.
- Stick to ISO 8601 and UTC for consistency and interoperability across systems.
Handling non-serializable types like datetimes is a common task when building data pipelines, APIs, or configuration systems. Mastering this pattern ensures your JSON data remains clean, portable, and easy to work with across different tools and languages.
Interactive Views
You are currently in ๐ All-in-One mode. Use the tabs at the top to switch to ๐ Theory Only or ๐ป Code Only views.
This section shows how to handle Python objects like datetimes that JSON cannot natively convert to strings.
๐งช Example 1: The Default JSON Serialization Error
This example demonstrates what happens when you try to serialize a datetime object without any special handling.
import json
from datetime import datetime
current_time = datetime.now()
data = {
"event": "deployment",
"timestamp": current_time
}
result = json.dumps(data)
๐ค Output: TypeError: Object of type datetime is not JSON serializable
๐งช Example 2: Manual String Conversion Before Serialization
This example shows how to manually convert a datetime to a string before passing it to JSON.
import json
from datetime import datetime
current_time = datetime.now()
data = {
"event": "deployment",
"timestamp": str(current_time)
}
result = json.dumps(data)
print(result)
๐ค Output: {"event": "deployment", "timestamp": "2025-04-08 14:30:00.123456"}
๐งช Example 3: Using a Custom Serialization Function
This example shows how to write a custom function that converts non-serializable types to strings.
import json
from datetime import datetime
def custom_serializer(obj):
if isinstance(obj, datetime):
return obj.isoformat()
raise TypeError(f"Type {type(obj)} not serializable")
current_time = datetime.now()
data = {
"event": "deployment",
"timestamp": current_time
}
result = json.dumps(data, default=custom_serializer)
print(result)
๐ค Output: {"event": "deployment", "timestamp": "2025-04-08T14:30:00.123456"}
๐งช Example 4: Using a Custom JSON Encoder Class
This example shows how to create a reusable JSON encoder class for handling datetimes.
import json
from datetime import datetime
class DateTimeEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime):
return obj.isoformat()
return super().default(obj)
current_time = datetime.now()
data = {
"event": "deployment",
"timestamp": current_time,
"status": "completed"
}
result = json.dumps(data, cls=DateTimeEncoder)
print(result)
๐ค Output: {"event": "deployment", "timestamp": "2025-04-08T14:30:00.123456", "status": "completed"}
๐งช Example 5: Handling Multiple Non-Serializable Types
This example shows how to handle both datetimes and other non-serializable types like sets.
import json
from datetime import datetime
class CustomEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime):
return obj.isoformat()
if isinstance(obj, set):
return list(obj)
return super().default(obj)
current_time = datetime.now()
tags = {"production", "critical", "v3"}
data = {
"event": "deployment",
"timestamp": current_time,
"tags": tags
}
result = json.dumps(data, cls=CustomEncoder)
print(result)
๐ค Output: {"event": "deployment", "timestamp": "2025-04-08T14:30:00.123456", "tags": ["production", "critical", "v3"]}
๐ Comparison Table: Handling Non-Serializable Types
| Method | Code Complexity | Reusability | Handles Multiple Types |
|---|---|---|---|
| Manual string conversion | Low | Low | No |
| Custom serializer function | Medium | Medium | Yes |
| Custom JSON encoder class | Medium | High | Yes |
Recommendation: Use a custom JSON encoder class when you need to handle datetimes across multiple files or projects.