The Right Way to Serialize Python Objects with Complex Attributes
Introduction
Serialization is the process of converting a Python object into a format that can be stored or transmitted, such as JSON. The json module in Python makes this task easy, but it comes with a limitation—only basic data types like strings, numbers, lists, and dictionaries are directly supported. When your object contains non-serializable attributes such as datetime objects, file handles, or custom classes, using json.dumps() will raise a TypeError.
This challenge is common in real-world applications, especially in web development, logging, and data storage. To overcome this, Python allows you to take control of how these special attributes are converted into JSON-friendly formats.

The Core Issue: Why TypeError Happens
JSON, as a data-interchange format, was designed to be simple and universal. It doesn’t natively understand Python-specific objects like datetime or Decimal. For example:
import json
from datetime import datetime
data = {"time": datetime.now()}
print(json.dumps(data)) # Raises TypeError
Here, the datetime object cannot be directly represented in JSON. JSON only knows how to handle primitive types like strings, integers, floats, booleans, arrays, and key-value mappings. Anything outside of this set must first be converted into one of these supported types.

The Step You Should Take: Use a Custom Encoder or default Parameter
The solution lies in teaching json.dumps() how to handle unsupported objects. This can be done by:
- Using the
defaultparameter
Thedefaultparameter injson.dumps()accepts a function that tells JSON how to handle non-serializable objects. For example: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") data = {"time": datetime.now()} print(json.dumps(data, default=custom_serializer))Here, thedatetimeobject is converted into an ISO 8601 string before being serialized. - Subclassing
JSONEncoder
Another approach is to extend Python’s built-inJSONEncoderclass and override itsdefaultmethod:import json from datetime import datetime class CustomEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, datetime): return obj.isoformat() return super().default(obj) data = {"time": datetime.now()} print(json.dumps(data, cls=CustomEncoder))This approach is cleaner when you need to handle multiple object types consistently.
Both methods give you control over how special Python objects are transformed into JSON-supported values.

Conclusion
When working with Python objects that include attributes not directly serializable to JSON, a simple call to json.dumps() will not work and will raise a TypeError. The essential step is to provide custom serialization logic—either through the default parameter or by subclassing JSONEncoder. This ensures that unsupported objects, like datetime, are first converted into strings or other supported formats before being serialized.
By taking this step, you make your Python objects compatible with JSON, enabling smooth data exchange in APIs, file storage, and communication across platforms. Ultimately, this approach ensures flexibility, clarity, and reliability in handling real-world data that often extends beyond simple types.