python - json 模块中的 object_hook 似乎没有按我的预期工作

标签 python json

我无法理解 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 函数?

最佳答案

通过实验,我已经回答了我自己的问题;这可能不是最好的解决方案,我欢迎进一步的分析或更好的方法,但这揭示了 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对象(而不仅仅是Person对象),但是这个解决方案确实提供了一种可扩展的自下而上的对象构造方法.

关于python - json 模块中的 object_hook 似乎没有按我的预期工作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43286178/

相关文章:

ruby-on-rails - 从 JSON 访问虚拟属性

javascript - 将变量传递给不同的函数

python - 目录递归仅深入一个目录

python - "Enabling"类比较

python - 绕过 "typing.Union is not a generic class"

python - 如何使用 boto 从作业中提取 FreeText 答案

python - 保存从 setup.py 生成的安装日志

json - 避免从表行构建的 JSON 中的匿名字段

javascript - 使用 angularjs 从 json 文件中删除项目

python - 使用多个 'for' 循环解码嵌套的 JSON