This is an archived version of the course and is no longer updated. Please find the latest version of the course on the main webpage.

JSON - Custom types

JSON works well when you use simple, supported, built-in types.

What if you want to serialise something more complicated, like a custom class?

JSON encoder of custom types

import json

class Vector: 
    def __init__(self, a, b): 
        self.a = a 
        self.b = b

    def __add__(self, other): 
        return Vector(self.a + other.a, self.b + other.b)

    def __str__(self): 
        return f"Vector ({self.a}, {self.b})"

v = Vector(3, 4) 
json.dumps(v)    ## TypeError: Object of type Vector is not JSON serializable

We receive an error complaint from Python, that Object of type Vector is not JSON serialisable.

So, how do we solve this problem?

  1. The first method would be the “Keep It Simple” method: Just represent your data with simple built-in types that we know JSON supports! I personally keep it that way. I would choose a different serialisation format if I need to serialise complicated objects.
  2. The second method is to exploit your newly acquired OOP prowess, and subclass the standard JSONEncoder and override its default() method. Ooh… behold the mighty power of OOP! 😆
class VectorEncoder(json.JSONEncoder): 
    def default(self, obj): 
        if isinstance(obj, Vector): 
            return (obj.a, obj.b)
        else: 
            return super().default(obj)

So in the code above, you override the default() method, and check if the object is a Vector. If it is, you return a tuple (a, b) which is supported by JSON. Otherwise, you just pass it back to the default() method of your superclass.

You can then just use the json.dump() method with the cls parameter to your new VectorEncoder:

json_string = json.dumps(v, cls=VectorEncoder)
print(json_string)   ## [3, 4]

Alternatively, you can invoke the encode() method of VectorEncoder.

encoder = VectorEncoder()
json_string = encoder.encode(v)
print(json_string)

JSON decoder of custom types

We have solved the issue of encoding, but what about deserialisation or decoding?

In our earlier example, our JSON string is "[3, 4]". If we try to decode this, we will end up with a list [3, 4], rather than a Vector. There is just not enough information to reconstruct Vector.

So we first need to modify our VectorEncoder from earlier so that it stores some information that this is a Vector. So this time, we use a dictionary rather than a tuple.

class VectorEncoder(json.JSONEncoder): 
    def default(self, obj): 
        if isinstance(obj, Vector): 
            return {"__vector__": True, "a": obj.a, "b": obj.b}
        else: 
            return super().default(obj)


v = Vector(3, 4) 
json_string = json.dumps(v, cls=VectorEncoder)    
print(json_string)  ## {"__vector__": true, "a": 3, "b": 4}

So now, we have stored Vector as a dictionary, with a key "__vector__" that will later tell us that this is meant to be a Vector.

Let us deserialise this json_string back into our Vector class then!

Like Encoding, we will use a custom decoder decode_vector to decode the json string back into a Vector. We use the object_hook parameter of json.loads() for it to use our custom decoder.

def decode_vector(dct): 
    if "__vector__" in dct: 
        return Vector(dct["a"], dct["b"])
    else:
        return dct

data = json.loads(json_string, object_hook=decode_vector)
print(data) ## Vector (3, 4)

And we have managed to reproduce our Vector from JSON!