【问题标题】:object_hook in json module doesn't seem to work as I'd expectjson 模块中的 object_hook 似乎没有像我预期的那样工作
【发布时间】:2017-09-03 07:31:56
【问题描述】:

我无法理解 json.loads() 中的 object_hook 功能是如何工作的。我在这里发现了一个类似的问题object_hook does not address the full json,但是我尝试按照我从中理解的内容进行操作,但它仍然对我不起作用。我已经收集到 object_hook 函数以某种方式被递归调用,但我无法理解如何使用它从 json 字符串构造复杂的对象层次结构。考虑以下 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 函数?

【问题讨论】:

  • 你确认person的内容是什么了吗?
  • 我意识到我忘了在上面发布我的 Person2 类;我将 Person 类中的地址分配更改为 self.address = address
  • @ScottHunter:如果你的意思是 p,是的;这是一个 Person 实例:<__main__.person instance at>。 (其他电话从来没有那么远。)

标签: python json


【解决方案1】:

通过实验,我已经回答了我自己的问题;这可能不是最好的解决方案,我欢迎进一步分析或更好的方法,但这阐明了 object_hook 过程的工作原理,因此它可能对面临相同问题的其他人具有指导意义。

关键的观察是,在 json 树遍历的每一层,object_hook 机制都希望你返回一个 dictionary,所以如果你想将子字典更改为类实例,你必须将当前 object_hook 函数调用的输入字典 values 替换为对象,而不仅仅是返回对象实例。

下面的解决方案允许建立对象层次结构的自下而上的方法。我插入了打印语句,以显示在处理 json 字符串的子部分时如何调用 load 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对象(而不仅仅是一个Person对象),但是这个解决方案确实提供了一种可扩展的自底向上对象构造方法.

【讨论】:

  • 做得很好,在我看来相当高效——无需道歉。由于缺乏其他答案,似乎大多数人——至少在这里——不知道如何做或一个好的方法(包括我自己)。感谢您启发我们。
【解决方案2】:

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

(您也可以看到,您也不需要所有那些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'))

【讨论】:

  • 这个例子的问题是 p.address 最终只是一个字典,而不是一个 Address 实例。您将如何修改它,以便我可以使用 object_hook 构建类实例层次结构(可能深几级)?
  • W.Sadkin:确实......在发布我的原始答案后我自己也想到了这一点 - 请参阅更新版本。
  • 我不确定这与我收到第一个回溯的第一个示例有何不同;唯一的区别似乎是您在 object_hook 函数中构造 Address 对象,而不是在 Person 对象的构造函数中。再一次,这感觉无论如何都是错误的方法,因为它实际上是在同一级别上进行所有字典到对象的转换。我想看到的是如何使用文档建议发生在 json 模块内部的树遍历构建对象层次结构,从最深层次到顶部。
  • @W.萨德金:“文档建议的树行走发生在哪里”?在我看来,它是自上而下的——我同意这对于做你想做的事情没有用。
  • 实际上,它不在文档中(这是问题的一部分),但在我在原始问题中引用的相关帖子中指出,这就是发生的事情,以及我的解决方案以上表明它确实是真的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-06-19
  • 2013-04-11
  • 1970-01-01
  • 1970-01-01
  • 2021-02-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多