How to write a custom JSON decoder for a complex object?
The encoder/decoder example you reference (here) could be easily extended to allow different types of objects in the JSON input/output.
However, if you just want a simple decoder to match your encoder (only having Edge objects encoded in your JSON file), use this decoder:
class EdgeDecoder(json.JSONDecoder): def __init__(self, *args, **kwargs): json.JSONDecoder.__init__(self, object_hook=self.object_hook, *args, **kwargs) def object_hook(self, dct): if 'Actor' in dct: actor = Actor(dct['Actor']['Name'], dct['Actor']['Age'], '') movie = Movie(dct['Movie']['Title'], dct['Movie']['Gross'], '', dct['Movie']['Year']) return Edge(actor, movie) return dct
Using the code from the question to define classes Movie
, Actor
, Edge
, and EdgeEncoder
, the following code will output a test file, then read it back in:
filename='test.json'movie = Movie('Python', 'many dollars', '', '2000')actor = Actor('Casper Van Dien', 49, '')edge = Edge(actor, movie)with open(filename, 'w') as jsonfile: json.dump(edge, jsonfile, cls=EdgeEncoder)with open(filename, 'r') as jsonfile: edge1 = json.load(jsonfile, cls=EdgeDecoder)assert edge1 == edge
This problem can be solved without the usage of JSONEncoder
or JSONDecoder
.
- add a
to_dict()
method to each class (takes care of the conversion frompython object
toJSON dict
) - if one of your object constructors expects parameter types other than
bool, str, int, and float
check whether the passed parameters are of typedict
, if that's the case you have to construct the object yourself (see constructor ofEdge
)
Shortened your example a bit:
class Edge: def __init__(self, actor, movie): if type(actor) is Actor: self.actor = actor else: # type == dict self.actor = Actor(**actor) if type(movie) is Movie: self.movie = movie else: # type == dict self.movie = Movie(**movie) def __eq__(self, other): return (self.movie == other.movie) & (self.actor == other.actor) def __str__(self): return "".join(["Actor: ", str(self.actor), " /// Movie: ", str(self.movie)]) def to_dict(self): return {"actor": self.actor.to_dict(), "movie": self.movie.to_dict()}class Movie: def __init__(self, title, gross, soup, year): self.title = title self.gross = gross self.soup = soup self.year = year def __eq__(self, other): return self.title == other.title def __str__(self): return self.title def to_dict(self): return {"title": self.title, "gross": self.gross, "soup": self.soup, "year": self.year}class Actor: def __init__(self, name, age, soup): self.name = name self.age = age self.soup = soup def __eq__(self, other): return self.name == other.name def __str__(self): return self.name def to_dict(self): return {"name": self.name, "age": self.age, "soup": self.soup}if __name__ == '__main__': edge_obj = Edge(Actor("Pierfrancesco Favino", 50, "id0"), Movie("Suburra", 10, "id1", 2015)) edge_dict = edge_obj.to_dict() edge_obj_new = Edge(**edge_dict) print("manual edge\t\t", edge_obj) print("edge to json\t", edge_dict) print("auto edge\t\t", edge_obj_new) print("edges equal?\t", edge_obj == edge_obj_new)
Returns:
manual edge Actor: Pierfrancesco Favino /// Movie: Suburraedge to json {'actor': {'name': 'Pierfrancesco Favino', 'age': 50, 'soup': 'id0'}, 'movie': {'title': 'Suburra', 'gross': 10, 'soup': 'id1', 'year': 2015}}auto edge Actor: Pierfrancesco Favino /// Movie: Suburraedges equal? True
As you can see both Edge
objects are equal and the second line outputs the Edge
as an dict
in JSON
notation.