2017-04-07 100 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

相關問題