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

时间:2014-10-29 21:30:42

标签: 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

观察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

当它看到包含键" name"的字典时,它无法决定是创建Group还是User对象(因此我返回None目前)。

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

s = """
{ "posts" : [ 
    {"title" : "Hello World", "user" : {"name" : "uli"}},
    {"title" : "Hello Group", "group" : {"name" : "Workgroup"}}
  ]
}
"""

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

如何以最佳方式解决这个问题? if中的dict_to_object - 陈述的积累是否真的要走了? (由于Complext嵌套的JSON结构,实际代码看起来更加混乱。)或者我应该使用其他任何模式或库吗? (虽然我更喜欢使用标准库。)

3 个答案:

答案 0 :(得分:0)

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

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

object_hook经常看起来很诱人,但很少是你想要的东西。仅仅因为它在那里,它往往是错误的选择。 如果你总是能够100%确定每个对象使用哪个类,那么它是唯一正确的选择(即便如此,只有在没有传递状态的情况下很容易评估,即在内部编写一个特殊的解析器你的对象钩子,通常是元素遵循一定的顺序,JSON永远不会格格不入等。

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

答案 1 :(得分:0)

一种方法是推迟创建UserGroup,直到您掌握了标记。也就是说,直到您创建Post

def dict_to_object(self, d):
    if "posts" in d:
        return d["posts"]
    if "title" in d:
        if "user" in d:
           d["user"] = User(d["user"]["name"])
        if "group" in d:
           d["group"] = Group(d["group"]["name"])
        return Post(d["title"],
                    d.get("user", None),
                    d.get("group", None))
    return d

答案 2 :(得分:0)

好的,这是我在没有继承json.JSONDecoder的情况下最终解决问题的方法:

class JSONDecoder:

    def decode_json(self, js):
        posts = []
        if "posts" in js:
            for p in js["posts"]:
                if "user" in p:
                    posts.append(Post(p["title"], user=self._decode_user(p["user"])))
                if "group" in p:
                    posts.append(Post(p["title"], group=self._decode_group(p["group"])))
        return posts

    def _decode_user(self, js):
        return User(js["name"])

    def _decode_group(self, js):
        return Group(js["name"])

可以使用JSONDecoder().decode_json(json.loads(s))调用。 BW:可以找到完整的代码on Bitbucket