How to write a custom JSON decoder for a complex object? How to write a custom JSON decoder for a complex object? python-3.x python-3.x

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 from python object to JSON dict)
  • if one of your object constructors expects parameter types other than bool, str, int, and float check whether the passed parameters are of type dict, if that's the case you have to construct the object yourself (see constructor of Edge)

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.