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.