我想解析从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
观察Group
和User
类具有相同的属性。现在我的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结构,实际代码看起来更加混乱。)或者我应该使用其他任何模式或库吗? (虽然我更喜欢使用标准库。)
答案 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)
一种方法是推迟创建User
和Group
,直到您掌握了标记。也就是说,直到您创建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。