python - object_hook in json module doesn't seem to work as I'd expect -
i'm having trouble understanding how object_hook functionality json.loads() works. found similar question object_hook not address full json here, i've tried follow understand it, , it's still not working me. gathered object_hook function called recursively in way, i'm failing understand how use construct complex object hierarchies json string. consider following json string, classes, , object_hook function:
import json 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 keyword args provide defaults json need not contain elements, , can still ensure attributes present in class instance. associate methods classes, want populate instances json data.)
if run:
>>> p = as_person(json.loads(jstr))
i expect, ie:
person found
and p becomes person object, ie:
>>> pprint(p.__dict__) {'address': <__main__.address instance @ 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, try 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 integers, not unicode
i have no idea why happen, , suspect there subtlety around how object_hook mechanism works i'm missing.
in attempt incorporate notion aforementioned question, object_hook evaluates each nested dictionary bottom (and replaces in traverse?) 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, proper form of object_hook function escaping me.
can explain in detail how object_hook mechanism works, , how resulting object tree supposed recursively constructed bottom up, why code doesn't work expected, , either fix example or provide 1 uses object_hook function build complex class, given 1 object_hook function?
through experimentation, have answered own question; may not best solution, , welcome further analysis or better way, sheds light on how object_hook process works, may instructive others facing same issues.
the key observation that, @ every level of json tree walk, object_hook mechanism expects return dictionary, if want change subdictionaries class instances, have replace current object_hook function invocation's input dictionary values objects, , not return object instances.
the solution below allows bottom-up means of building object hierarchy. i've inserted print statements show how loads object_hook called on on subsections of json string it's processed, found quite illuminating, , helpful me in build working function.
import json 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'))} >>>
note returned still dictionary, key 'person', , value person object (rather person object), solution provide extensible bottom-up object construction method.
Comments
Post a Comment