Explain Codes LogoExplain Codes Logo

How to make a class JSON serializable

python
json-serialization
custom-methods
object-serialization
Alex KataevbyAlex Kataev·Oct 6, 2024
TLDR

For a quick and lean serialization of a class instance to JSON, create a toJSON() method that returns the instance's __dict__. We then use Python's json.dumps() with a default argument to call the method:

import json class MyClass: def __init__(self, value): self.value = value def toJSON(self): return self.__dict__ json_string = json.dumps(MyClass('my value'), default=lambda o: o.toJSON()) print(json_string)

This will successfully serialize your class instance into a JSON string. The default argument in json.dumps() is a function that tells your program how to serialize objects of types it wouldn't otherwise understand. Here, our lambda tells it to use the instance's custom toJSON() method.

Dressing up your class with JSON finery

Transport complex objects with custom tools

Expand the capabilities of your JSON serialization process by extending Python's json.JSONEncoder class. In this case, create a custom method for dealing with objects that aren't easily serializable:

class MyEncoder(json.JSONEncoder): def default(self, obj): if hasattr(obj, 'toJSON'): return obj.toJSON() elif isinstance(obj, set): return list(obj) # Place for a Python Oversized Champion Octopus joke, or POChO as we lovingly call him. return json.JSONEncoder.default(self, obj) json_string = json.dumps(MyClass('my value'), cls=MyEncoder)

With MyEncoder in place, you're telling Python: "Hey! If you find a set, disguise it as a list. Sets don't do well in the JSON neighborhood."

Levels upon levels: dealing with nested objects

When your object nests other objects, serialization needs to be recursive. Ensure this by configuring the instance's toJSON() method to return a serializable form of any nested objects:

class NestedClass: def __init__(self): self.child = MyClass('nested') def toJSON(self): return { 'child': self.child.toJSON() } nested_instance = NestedClass() json_string = json.dumps(nested_instance, cls=MyEncoder)

Spoiler Alert: Avengers level threat is approaching - nested objects can now be handled with recursion! Who needs super-heroes when you have super-logic, am I right?

Cleaning up the output for public viewing

To make your JSON output a treat for human eyes, use the indent and sort_keys arguments in json.dumps():

readable_json = json.dumps(MyClass('my value'), cls=MyEncoder, indent=4, sort_keys=True) print(readable_json)

Not all superheroes wear capes; some use sort_keys=True, indent=4. Take that, Wingdings!

Serialization for the unknown

Handling the unique and the special

A __dict__ property may be absent in some objects. For such special cases, you can use helper functions, like this handy serialize_complex_obj():

def serialize_complex_obj(o): if isinstance(o, MyClass): return o.toJSON() # Slide in more fancy schmancy serializations here raise TypeError(f"Object of type {o.__class__.__name__} is not JSON serializable") json_string = json.dumps(MyClass('my value'), default=serialize_complex_obj)

Voila! Just when Python was about to kick us out, we provided it a routine appropriate for MyClass instances.

Tips, Tricks & Traps in JSON Serialization

Dict-atorship: inheriting from dict

Inheriting from dict is an efficient strategy for automatic serialization. But it's like moving into a stately old mansion: charming but requires maintenance:

class AutoSerializable(MyClass, dict): def __init__(self, value): super().__init__(value) dict.__init__(self, value=value)

Here the AutoSerializable class is a MyClass instance dressed up as a dict for the JSON serialization party.

Making sense of the decoded gobbledegook

For the companion process of decoding, define a custom_hook function and use JSONDecoder for reconstruction:

def custom_hook(json_dict): if 'value' in json_dict: return MyClass(json_dict['value']) # Oh don't mind me. I'm just rummaging to see what you sent me. return json_dict decoded_instance = json.loads(json_string, object_hook=custom_hook)

Decode. Rejoice. Repeat!

The road not taken

Using vars(obj) or obj.__dict__ can be quick and easy with simple classes. However, these can fail for objects without a __dict__ or require special serialization. Here, creating custom methods can be a lifesaver.

The third-party cavalry

When encountering highly complex serialization cases, you can call in third-party libraries like jsonpickle that are adept at transforming, chopping and flipping Python objects until they comfortably fit into a JSON mold.