2017-04-07 99 views
1

我无法理解json.loads()中的object_hook功能是如何工作的。我在这里发现了一个类似的问题object_hook does not address the full json,但我试图按照我所了解的内容进行操作,但它仍然不适用于我。我已经收集到以某种方式递归调用object_hook函数,但我无法理解如何使用它从json字符串构造复杂的对象层次结构。请看下面的JSON字符串,类和object_hook功能:json模块中的object_hook似乎并不像我期望的那样工作

import json 
from pprint import pprint 

jstr = '{"person":{ "name": "John Doe", "age": "46", \ 
      "address": {"street": "4 Yawkey Way", "city": "Boston", "state": "MA"} } }' 

class Address: 
    def __init__(self, street=None, city=None, state=None): 
     self.street = street 
     self.city = city 
     self.state = state 

class Person: 
    def __init__(self, name=None, age=None, address=None): 
     self.name = name 
     self.age = int(age) 
     self.address = Address(**address) 

def as_person(jdict): 
    if u'person' in jdict: 
     print('person found') 
     person = jdict[u'person'] 
     return Person(name=person[u'name'], age=person[u'age'], 
         address=person[u'address']) 
    else: 
     return('person not found') 
     return jdict 

(我定义与关键字ARGS类提供的默认值,这样的JSON不需要包含所有要素,我还是可以保证的属性存在于。类的实例我也将最终关联方法与类,但想从JSON数据填充实例)

如果我运行:

>>> p = as_person(json.loads(jstr)) 

我得到了我希望,即:

person found 

和P成为一个Person对象,即:

>>> pprint(p.__dict__) 
{'address': <__main__.Address instance at 0x0615F3C8>, 
'age': 46, 
'name': u'John Doe'} 
>>> pprint(p.address.__dict__) 
{'city': u'Boston', 'state': u'MA', 'street': u'4 Yawkey Way'} 

然而,如果不是,我尝试使用:

>>> p = json.loads(jstr, object_hook=as_person) 

我得到:

person found 
Traceback (most recent call last): 
    File "<interactive input>", line 1, in <module> 
    File "C:\Program Files (x86)\Python27\lib\json\__init__.py", line 339, in loads 
    return cls(encoding=encoding, **kw).decode(s) 
    File "C:\Program Files (x86)\Python27\lib\json\decoder.py", line 366, in decode 
    obj, end = self.raw_decode(s, idx=_w(s, 0).end()) 
    File "C:\Program Files (x86)\Python27\lib\json\decoder.py", line 382, in 
raw_decode 
    obj, end = self.scan_once(s, idx) 
    File "<interactive input>", line 5, in as_person 
TypeError: string indices must be integers, not unicode 

我不知道为什么会发生这种情况,并怀疑有关object_hook机制如何工作的细节我错过了。

在试图以纳入上述问题,这是该object_hook评估从下往上每个嵌套的字典(?和横向将其替换)的概念,我也试过:

def as_person2(jdict): 
    if u'person' in jdict: 
     print('person found') 
     person = jdict[u'person'] 
     return Person2(name=person[u'name'], age=person[u'age'], address=person[u'address']) 
    elif u'address' in jdict: 
     print('address found') 
     return Address(jdict[u'address']) 
    else: 
     return('person not found') 
     return jdict 

>>> json.loads(jstr, object_hook=as_person2) 
address found 
person found 
Traceback (most recent call last): 
    File "<interactive input>", line 1, in <module> 
    File "C:\Program Files (x86)\Python27\lib\json\__init__.py", line 339, in loads 
    return cls(encoding=encoding, **kw).decode(s) 
    File "C:\Program Files (x86)\Python27\lib\json\decoder.py", line 366, in decode 
    obj, end = self.raw_decode(s, idx=_w(s, 0).end()) 
    File "C:\Program Files (x86)\Python27\lib\json\decoder.py", line 382, in raw_decode 
    obj, end = self.scan_once(s, idx) 
    File "<interactive input>", line 5, in as_person2 
AttributeError: Address instance has no attribute '__getitem__' 

所以显然,object_hook函数的正确形式正在逃避我。

有人可以详细解释object_hook机制如何工作,以及如何从底层向上递归构造生成的对象树,为什么我的代码无法按预期工作,并修复我的示例或提供一个使用object_hook函数来构建一个复杂的类,因为你只能得到一个object_hook函数?

+0

您确认了“人”的内容吗? –

+0

我意识到我忘了上面发布我的Person2类;我更改了Person类中的地址分配,只是说 self.address =地址 –

+0

@ScottHunter:如果你的意思是p,是的;它是一个Person实例: <__ main __。0x0615F148>个人实例>。 (其他调用从来没有那么远。) –

回答

0

我同意这是不直观的,但你可以简单地忽略传递的字典时,它不是你感兴趣的类的对象,这意味着,这将可能是最简单的方法:

(如你也可以看到,你并不需要所有这些u字符串前缀,要么)

import json 

jstr = '{"person": { "name": "John Doe", "age": "46", \ 
      "address": {"street": "4 Yawkey Way", "city": "Boston", "state": "MA"} } }' 

class Address: 
    def __init__(self, street=None, city=None, state=None): 
     self.street = street 
     self.city = city 
     self.state = state 

    def __repr__(self): # optional - added so print displays something useful 
     return('Address(street={self.street!r}, city={self.city!r}, ' 
       'state={self.state!r})'.format(self=self)) 

class Person: 
    def __init__(self, name=None, age=None, address=None): 
     self.name = name 
     self.age = int(age) 
     self.address = address 

    def __repr__(self): # optional - added so print displays something useful 
     return('Person(name={self.name!r}, age={self.age!r},\n' 
       '  address={self.address!r})'.format(self=self)) 

def as_person3(jdict): 
    if 'person' not in jdict: 
     return jdict 
    else: 
     person = jdict['person'] 
     address = Address(**person['address']) 
     return Person(name=person['name'], age=person['age'], address=address) 

p = json.loads(jstr, object_hook=as_person3) 
print(p) 

输出:

Person(name=u'John Doe', age=46, 
     address=Address(street=u'4 Yawkey Way', city=u'Boston', state=u'MA')) 
+0

该示例的问题是,p.address最终只是一个字典,而不是一个Address实例。你将如何修改这个,以便我可以使用object_hook建立类实例层次结构(可能有几个层次)? –

+0

W.Sadkin:确实......在发布我的原始答案后,我想了一会儿,看看更新后的版本。 – martineau

+0

我不确定这与我的第一个例子有什么不同,我收到了第一个回溯;唯一的区别似乎是你在object_hook函数中构造Address对象,而不是在Person对象的构造函数中。而且,无论如何,这感觉就像是错误的方法,因为它实际上是在做所有字典来对对象转换进行同一级别的处理。我希望看到的是如何使用树形结构构建对象层次结构,文档建议在json模块内部从最深层次到顶层发生。 –

1

通过实验,我回答了我自己的问题;这可能不是最好的解决方案,我欢迎进一步的分析或更好的方法,但是这揭示了object_hook过程如何工作,因此对于面临相同问题的其他人可能会有启发性。

关键的观察结果是,在JSON树行走的每一个层面,object_hook机制,希望你返回字典,所以如果你想改变分字典成类实例,你要替换当前object_hook函数调用的输入字典与对象,而不只是返回对象实例。

下面的解决方案允许建立对象层次结构的自下而上的方法。我已经插入了打印语句,以显示在处理json字符串的子部分时如何调用加载object_hook,我发现这很明显,并且对构建工作函数有帮助。

import json 
from pprint import pprint 

jstr = '{"person":{ "name": "John Doe", "age": "46", \ 
     "address": {"street": "4 Yawkey Way", "city": "Boston", "state": "MA"} } }' 

class Address: 
    def __init__(self, street=None, city=None, state=None): 
     self.street=street 
     self.city=city 
     self.state = state 
    def __repr__(self): 
     return('Address(street={self.street!r}, city={self.city!r},' 
         'state={self.state!r})'.format(self=self)) 

class Person: 
    def __init__(self, name=None, age=None, address=None): 
     self.name = name 
     self.age = int(age) 
     self.address=address 
    def __repr__(self): 
     return('Person(name={self.name!r}, age={self.age!r},\n' 
       '  address={self.address!r})'.format(self=self)) 

def as_person4(jdict): 
    if 'person' in jdict: 
     print('person in jdict; (before substitution):') 
     pprint(jdict) 
     jdict['person'] = Person(**jdict['person']) 
     print('after substitution:') 
     pprint(jdict) 
     print 
     return jdict 
    elif 'address' in jdict: 
     print('address in jdict; (before substitution):'), 
     pprint(jdict) 
     jdict['address'] = Address(**jdict['address']) 
     print('after substitution:') 
     pprint(jdict) 
     print 
     return jdict 
    else: 
     print('jdict:') 
     pprint(jdict) 
     print 
     return jdict 

>>> p =json.loads(jstr, object_hook=as_person4) 
jdict: 
{u'city': u'Boston', u'state': u'MA', u'street': u'4 Yawkey Way'} 

address in jdict; (before substitution): 
{u'address': {u'city': u'Boston', u'state': u'MA', u'street': u'4 Yawkey Way'}, 
u'age': u'46', u'name': u'John Doe'} 
after substitution: 
{u'address': Address(street=u'4 Yawkey Way', city=u'Boston', state=u'MA'), 
u'age': u'46', u'name': u'John Doe'} 

person in jdict; (before substitution): 
{u'person': {u'address': Address(street=u'4 Yawkey Way', city=u'Boston', state=u'MA'), 
     u'age': u'46', u'name': u'John Doe'}} 
after substitution: 
{u'person': Person(name=u'John Doe', age=46, 
    address=Address(street=u'4 Yawkey Way', city=u'Boston', state=u'MA'))} 

>>> p 
{u'person': Person(name=u'John Doe', age=46, 
    address=Address(street=u'4 Yawkey Way', city=u'Boston', state=u'MA'))} 
>>> 

注意返回什么仍然是一个字典,这里的关键是“人”,和值是Person对象(而不仅仅是一个Person对象),但这种方法并提供一个可扩展的自下而上建立对象的方法。

+0

做得很好,看起来相当有效 - 不需要道歉。从缺乏其他答案看,似乎是大多数人 - 至少在这里 - 并不知道如何或一个好的方法(包括我自己)。感谢您启发我们。 – martineau

相关问题