Как написать собственный декодер JSON для сложного объекта?

Как следует из названия, я пытаюсь написать собственный декодер для объекта, класс которого я определил, который содержит другие объекты, класс которых я определил. «Внешний» класс — это Edge, определенный следующим образом:

class Edge:
    def __init__(self, actor, movie):
        self.actor = actor
        self.movie = movie

    def __eq__(self, other):
        if (self.movie == other.movie) & (self.actor == other.actor):
            return True
        else:
            return False

    def __str__(self):
        print("Actor: ", self.actor, " Movie: ", self.movie)

    def get_actor(self):
        return self.actor

    def get_movie(self):
        return self.movie

с "внутренними" классами актера и фильма, определенными следующим образом:

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):
        if self.title == other.title:
            return True
        else:
            return False

    def __repr__(self):
        return self.title

    def __str__(self):
        return self.title

    def get_gross(self):
        return self.gross

    def get_soup(self):
        return self.soup

    def get_title(self):
        return self.title

    def get_year(self):
        return self.year

class Actor:
    def __init__(self, name, age, soup):
        self.name = name
        self.age = age
        self.soup = soup

    def __eq__(self, other):
        if self.name == other.name:
            return True
        else:
            return False

    def __repr__(self):
        return self.name

    def __str__(self):
        return self.name

    def get_age(self):
        return self.age

    def get_name(self):
        return self.name

    def get_soup(self):
        return self.soup

(суп — это просто красивый объект супа для страницы Википедии этого фильма/актера, его можно игнорировать). Я также написал клиентский кодировщик для класса Edge:

class EdgeEncoder(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, Edge):
            return {
                    "Actor": {
                             "Name": o.get_actor().get_name(),
                             "Age": o.get_actor().get_age()
                             },
                    "Movie": {
                             "Title": o.get_movie().get_title(),
                             "Gross": o.get_movie().get_gross(),
                             "Year": o.get_movie().get_year()
                             }
                    }
        return json.JSONEncoder.default(self, o)

который я тестировал, и он правильно сериализует список ребер в файл JSON. Теперь моя проблема возникает при попытке написать пограничный декодер. Я использовал страницу github здесь в качестве справки, но мой кодировщик отличается от его и моего. м интересно, нужно ли его менять. Нужно ли мне явно кодировать тип объекта как его собственную пару ключ-значение в его сериализации JSON, как это делает он, или есть какой-то способ получить ключи «Актер» и «Кино» с сериализацией края? Точно так же есть способ получить «Имя». «Возраст» и т. д., чтобы я мог реконструировать объект «Актер/Кино», а затем использовать их для реконструирования края? Есть ли лучший способ вместо этого кодировать мои объекты? Я также пробовал следовать этот учебник, но я обнаружил, что использование объектных диктов сбивает с толку их кодировщик, и я не был уверен, как расширить этот метод на пользовательский объект, который содержит пользовательские объекты.


person lmotl3    schedule 26.02.2018    source источник


Ответы (2)


Пример кодировщика/декодера, на который вы ссылаетесь (здесь), можно легко расширить, чтобы разрешить различные типы объектов в ввод/вывод JSON.

Однако, если вы просто хотите, чтобы простой декодер соответствовал вашему кодировщику (только с объектами Edge, закодированными в вашем файле JSON), используйте этот декодер:

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

Используя код из вопроса для определения классов Movie, Actor, Edge и EdgeEncoder, следующий код выведет тестовый файл, а затем прочитает его обратно:

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
person VirtualScooter    schedule 03.09.2018

Эту проблему можно решить без использования JSONEncoder или JSONDecoder.

  • добавить метод to_dict() к каждому классу (заботится о преобразовании из python object в JSON dict)
  • если один из ваших конструкторов объектов ожидает типы параметров, отличные от bool, str, int, and float, проверьте, имеют ли переданные параметры тип dict, если это так, вам нужно создать объект самостоятельно (см. конструктор Edge)

Немного сократил ваш пример:

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)

Возвращает:

manual edge      Actor: Pierfrancesco Favino /// Movie: Suburra
edge 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: Suburra
edges equal?     True

Как видите, оба объекта Edge равны, и вторая строка выводит Edge как dict в нотации JSON.

person user3608078    schedule 19.06.2018