如何为复杂对象编写自定义 JSON 解码器?

Posted

技术标签:

【中文标题】如何为复杂对象编写自定义 JSON 解码器?【英文标题】:How to write a custom JSON decoder for a complex object? 【发布时间】:2018-08-06 02:39:14 【问题描述】:

正如标题所说,我正在尝试为我定义的类包含我定义的其他对象的对象编写自定义解码器。 “外部”类是一个 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

“内部”类 actor 和 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

(soup 只是该电影/演员的***页面的一个 beautifulsoup 对象,可以忽略)。 我也为边缘类编写了一个客户编码器:

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 页面 here 作为参考,但是我的编码器与他的有所不同,我想知道是否有必要更改它。我是否需要像他那样在其 JSON 序列化中将对象的类型显式编码为其自己的键值对,或者是否有某种方法可以通过边缘序列化来获取“演员”和“电影”键?同样,有没有办法抓住“名称”。 “年龄”等,以便我可以重建 Actor/Movie 对象,然后使用它们来重建边缘?有没有更好的方法来编码我的对象?我也尝试过遵循this 教程,但我发现对象字典的使用对他们的编码器来说很混乱,而且我不确定如何将该方法扩展到包含自定义对象的自定义对象。

【问题讨论】:

【参考方案1】:

您引用的编码器/解码器示例 (here) 可以轻松扩展以在 JSON 输入/输出中允许不同类型的对象。

但是,如果您只想要一个简单的解码器来匹配您的编码器(仅在 JSON 文件中编码 Edge 对象),请使用此解码器:

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

使用问题中的代码定义类MovieActorEdgeEdgeEncoder,以下代码将输出一个测试文件,然后将其读回:

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

【讨论】:

【参考方案2】:

这个问题可以不用JSONEncoder或者JSONDecoder来解决。

为每个类添加一个to_dict() 方法(负责从python objectJSON 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 对象相等,第二行以 JSON 表示法将 Edge 输出为 dict

【讨论】:

以上是关于如何为复杂对象编写自定义 JSON 解码器?的主要内容,如果未能解决你的问题,请参考以下文章

如何为计时时间戳使用自定义 serde 反序列化器?

如何为 PyYAML 编写表示器?

System.Text.Json:如何为枚举值指定自定义名称?

如何为spring数据编写自定义模块

如何为自定义结构中定义的无序集编写自定义哈希函数?

如何为 Presto 编写自定义窗口函数?