The question title captures what I want to do. But to make it concrete, let's assume I have this schema (which is a schema definition in terms of python-jsonschema: https://python-jsonschema.readthedocs.io/en/stable/):
schema = {
"type" : "object",
"properties" : {
"price" : {"type" : "number"},
"name" : {"type" : "string"},
},
}
With this being a valid document:
{"name" : "Eggs", "price" : 34.99}
and these two classes:
class Price:
def __init__(self, price: float):
self.price = price
class Object:
def __init__(self, name: str, price: float):
self.name = name
self.price = Price(price)
Below I list common solutions and my reservations with them. My question is thus whether either there is either another method I don't know of, or if my reservations for any of the below solutions is misplaced. Or, simply put, what is best practice?
Method 1: Use objects' __dict__ representation and then serialise with json.dumps() (see this answer).
Reservation: This couples the object to the schema. For example, I now have to name object properties with the property names required by the schema. The reasons why this is bad are obvious: backward compatibility problems, conflicts with coding style guide, design lock-in etc. There is also a comment on the linked answer with 47 upvotes arguing that "Using __dict__ will not work in all cases. If the attributes have not been set after the object was instantiated, __dict__ may not be fully populated." I confess I don't understand. But it sounds bad.
Method 2: Subclass JSONEncoder (also in this answer)
Reservation: This seems helpful organisationally but it still begs the question of how to implement the default method without calling __dict__ anyway and having the same problem as above.
Method 3: write a custom asdict on each class. This is what I'm currently doing. It looks something like this:
class Price:
def __init__(self, price: float):
self.price = price
def asdict(self):
# if we had:
# return {"price": self.price}
# Then the nesting mismatch between the class hierachy and the schema would cause a problem.
return self.price
class Object:
def __init__(self, name: str, price: float):
self.name = name
self.price = Price(price)
def asdict(self):
return {"name": self.name, "price": self.price.asdict()}
Reservations: most clearly, there is now the problem that my class hierachy becomes coupled to the nesting structure of the schema. You can see above the problem this has caused. More seriously though, it means my serialisation definition is spread over multiple asdict() methods in multiple different classes. What I want is to have a file called "serializers.py" that completely specifies the process of converting my class hierachy to JSON. Not disperate asdict() methods all over my code.
Any advice?