如何正确地将JSON转换为Python对象?

Rob*_*ert 5 python json json-deserialization

我想将从 Web 服务获取的 JSON 解析为对象结构。因此,我正在json.JSONDecoder用一个object_hook方法实现 的子类。我还没有找到一个好方法来为给定的数据选择正确的类。对于具有相同属性的类,似乎不可能识别正确的属性,因为这需要知道密钥。让我们看一个例子:

我有以下课程:

class Post:
    def __init__(self, title, user=None, group=None):
        self.title = title
        self.user = user
        self.group = group

class Group:
    def __init__(self, name):
        self.name = name

class User:
    def __init__(self, name):
        self.name = name
Run Code Online (Sandbox Code Playgroud)

观察 和GroupUser具有相同的属性。现在我的 JSONDecoder 看起来像这样:

 class JSONDecoder(json.JSONDecoder):

    def __init__(self, encoding="UTF-8"):
        json.JSONDecoder.__init__(self, object_hook=self.dict_to_object)

    def dict_to_object(self, d):

        if "posts" in d:
            return d["posts"]
        if "title" in d:
            if "user" in d:
               return Post(d["title"], user=d["user"])
            if "group" in d:
               return Post(d["title"], group=d["group"])
        if "name" in d:
            # How to decide if User(d["name"]) or Group(d["name")?
            return None
        return None
Run Code Online (Sandbox Code Playgroud)

当它看到包含键“name”的字典时,它无法决定是否创建一个Group或一个User对象(因此我None现在返回)。

我想要解析的 JSON 字符串如下所示:

s = """
{ "posts" : [ 
    {"title" : "Hello World", "user" : {"name" : "uli"}},
    {"title" : "Hello Group", "group" : {"name" : "Workgroup"}}
  ]
}
"""
Run Code Online (Sandbox Code Playgroud)

这应该会产生一个 Post 对象列表,每个对象都有一个标题和一个组或用户。

如何以最好的方式解决这个问题?这种if-Statements的积累dict_to_object真的是正确的方法吗?(由于复杂的嵌套 JSON 结构,实际代码看起来更加混乱。)或者我应该使用其他模式或库吗?(尽管我更喜欢使用标准库。)

smc*_*mci 0

在这种情况下,IME 通常使用 JSON 解码,最好在解码时分配给通用字典,不要使用 object_hook,并推迟创建单独的类型化对象,直到所有解码后的第二次传递,此时您可以任意检查您正在处理的流和层次结构,即哪个对象是哪个对象的父/子/兄弟。(就像@BrenBam 说的)

使用 classmethod make_xyz 函数,而不是构造函数

object_hook通常看起来很诱人,但很少是您想要的东西。仅仅因为它的存在,它往往是错误的选择。只有当您始终能够 100% 确定每个对象使用哪个类时,这才是正确的选择(即使如此,只有在不传递状态的情况下很容易评估,即在对象挂钩中编写一个临时解析器时,这才是正确的选择) ),通常元素遵循一定的顺序,JSON 永远不会格式错误等。

在这里,您遇到了一个通用问题:在这种特定情况下,构造函数{"name" : "xyz"}无法知道它是什么类型的 JSON 对象,只有看到的父对象"user"/"group" :可以。一种解决方案是将所有类和构造函数重构为 classmethods make_group()make_user()。但这只是将第二次解码传递到第一次解码传递中,没有特殊原因,给了我们一个巨大的脆弱的 object_hook 函数。IME 这很少是一个好主意。