I'm having trouble understanding how the object_hook functionality from json.loads() actually works. I found a similar question object_hook does not address the full json here, but I've tried to follow what I understand from it, and it's still not working for me. I already gathered that the object_hook function is called recursively in some way, but I'm failing to understand how to use it to construct complex object hierarchies from the json string. Consider the following json string, classes, and object_hook function:
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
(I define classes with keyword args to provide defaults so that the json need not contain all elements, and I can still ensure that the attributes are present in the class instance. I will also eventually associate methods with the classes, but want to populate the instances from json data.)
If I run:
>>> p = as_person(json.loads(jstr))
I get what I expect, ie:
person found
and p becomes a Person object, ie:
>>> 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'}
However, if instead, I try to use:
>>> p = json.loads(jstr, object_hook=as_person)
I get:
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
I have no idea why this would happen, and suspect there is some subtlety around how the object_hook mechanism works that I'm missing.
In an attempt to incorporate the notion from the aforementioned question, which was that the object_hook evaluates each nested dictionary from the bottom up (and replaces it in the traverse?) I also tried:
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__'
So, clearly, the proper form of the object_hook function is escaping me.
Can someone explain in detail how the object_hook mechanism works, and how the resulting object tree is supposed to be recursively constructed from the bottom up, why my code doesn't work as expected, and either fix my example or provide one that uses an object_hook function to build a complex class, given that you only get the one object_hook function?