Python Register Classes

Posted on 05/10/2020 in python

serilize object and deserilize a json

In [7]:
import json
In [8]:
class Serializable(object):
    def __init__(self, *args):
        self.args = args
    
    def serialize(self):
        return json.dumps({'args': self.args})
In [9]:
class Point2D(Serializable):
    def __init__(self, x, y):
        super().__init__(x, y)
        self.x = x
        self.y = y
    
    def __repr__(self):
        return 'Point2D ({},{})'.format(self.x,self.y)
    
point = Point2D(5,3)
print('Object: ', point)
print('Serialized: ', point.serialize())
Object:  Point2D (5,3)
Serialized:  {"args": [5, 3]}
In [10]:
class Deserializable(Serializable):
    @classmethod
    def deserialize(cls, json_data):
        params = json.loads(json_data)
        return cls(*params['args'])
In [13]:
class BetterPoint2D(Deserializable):
    def __init__(self, x, y):
        super().__init__(x, y)
        self.x = x
        self.y = y
    
    def __repr__(self):
        return 'Point2D ({},{})'.format(self.x,self.y)
In [15]:
point = BetterPoint2D(5,3)
print('Before: ', point)
data = point.serialize()
print('Serialized: ', data)
after = BetterPoint2D.deserialize(data)
print('After: ', after)
Before:  Point2D (5,3)
Serialized:  {"args": [5, 3]}
After:  Point2D (5,3)

The problem with above approach is that it only works if you know the inteded type of the serialized data ahead of time (eg, Point2D)

To solve this, we need a more generic solution

In [17]:
class Serializable(object):
    def __init__(self, *args):
        self.args = args
    
    def serialize(self):
        return json.dumps({'class': self.__class__.__name__,
                          'args': self.args})
    
    def __repr__(self):
        return self.__class__.__name__
In [22]:
registry = {}

def register_class(target_class):
    registry[target_class.__name__] = target_class

def deserialize(data):
    params = json.loads(data)
    name = params['class']
    target_class = registry[name]
    return target_class(*params['args'])
In [23]:
class EvenBetterPoint2D(BetterSerializable):
    def __init__(self, x, y):
        super().__init__(x, y)
        self.x = x
        self.y = y

register_class(EvenBetterPoint2D)
In [25]:
point = EvenBetterPoint2D(5,3)
print('Before: ', point)
data = point.serialize()
print('Serialized: ', data)
after = deserialize(data)
print('After: ', after)
Before:  EvenBetterPoint2D
Serialized:  {"class": "EvenBetterPoint2D", "args": [5, 3]}
{'class': 'EvenBetterPoint2D', 'args': [5, 3]}
After:  EvenBetterPoint2D

The problem with above approach is we might forget registering class

In [26]:
class Meta(type):
    def __new__(meta, name, bases, class_dict):
        cls = type.__new__(meta, name, bases, class_dict)
        register_class(cls)
        return cls

class RegisteredSerializable(BetterSerializable, metaclass=Meta):
    pass
In [28]:
class Vector3D(RegisteredSerializable):
    def __init__(self, x, y, z):
        super().__init__(x,y,z)
        self.x, self.y, self.z = x ,y, z

v3 = Vector3D(10, -7, 3)
print('Before: ', v3)
data = v3.serialize()
print('Serialized: ', data)
print('After: ', deserialize(data))
Before:  Vector3D
Serialized:  {"class": "Vector3D", "args": [10, -7, 3]}
{'class': 'Vector3D', 'args': [10, -7, 3]}
After:  Vector3D