json dumping(serializing) custom python classes
One of the greatest features of Python is that native types are naturally JSON serializable which makes it exporting/importing json files so easy and convenient without any hassle.
However, once the user starts to create their own classes and confronts the situation to dump them as a part of a JSON exportable type, then things start to get out of hand.
For example, when a project starts to get complicated and I create several classes of my own and as always, print some debugging outputs, I usually simply dump objects of interest into a json file for later inspection. This is when I face difficulties because the classes that I made are not naturally JSON serialized. Here’s an example
import json
class ParentClass:
def \_\_init\_\_(self, id, val):
self.\_id = id
self.\_val = val
def tojson(self):
return {
"id": self.\_id,
"val": self.\_val
}
t = ParentClass(0, "something")
wrap = {
"data": \[t\],
"blah": 1
}
print(json.dumps(wrap, indent=4))
This results in an error:
File "/usr/lib/python3.6/json/encoder.py", line 437, in \_iterencode
o = \_default(o)
File "/usr/lib/python3.6/json/encoder.py", line 180, in default
o.\_\_class\_\_.\_\_name\_\_)
TypeError: Object of type 'ParentClass' is not JSON serializable
AFAIK, there is no way to make my class be naturally JSON serializable. However, there is a hack for this.
The idea is to use a custom json encoder. This custom json encoder will json serialize as normal when encountering the native python object types. However, when it faces a custom class, instead of raising an error, it will check if it has tojson
method and if it does, it will call this method and retrieve an object that consists of json serializable objects. Here is the code for this idea:
import json
class ParentClass:
def \_\_init\_\_(self, id, val):
self.\_id = id
self.\_val = val
def tojson(self):
return {
"id": self.\_id,
"val": self.\_val
}
class SubClass:
def \_\_init\_\_(self, data):
self.\_data = data
def tojson(self):
return {
"data": self.\_data
}
class CustomEncoder(json.JSONEncoder):
def default(self, o):
if "tojson" in dir(o):
return o.tojson()
return json.JSONEncoder.default(self, o)
s= SubClass("blah")
t = ParentClass(0, s)
wrap = {
"data": \[t\],
"blah": 1
}
print(json.dumps(wrap, indent=4, cls=CustomEncoder))
And here is the result:
{
"data": \[
{
"id": 0,
"val": {
"data": "blah"
}
}
\],
"blah": 1
}
And it worked! Another important point to note here is how the CustomEncoder
will also take care of subclasses that needs to be dealt in our own special way. The SubClass
also has implemented tojson
method which is called recursively when json serializing ParentClass
object.
Although this approach does require the hassle of configuring the cls
parameter whenever calling json.dump
and ensuring that any custom classes that faces the situation of json serializing will be required to implement a tojson
sort of method, I believe this is still the neatest way of doing it so far.