2017-06-01 117 views
3

在過去,我使用go以如下所示的方式解碼來自API端點的JSON。Go - 解碼JSON,因爲它仍然通過網絡/ http進行流式傳輸

client := &http.Client{} 

req, err := http.NewRequest("GET", "https://some/api/endpoint", nil) 
res, err := client.Do(req) 
defer res.Body.Close() 

buf, _ := ioutil.ReadAll(res.Body) 

// ... Do some error checking etc ... 

err = json.Unmarshal(buf, &response) 

我不久將要工作的,可以送我JSON數據的幾兆字節的格式如下的端點。

{ 
    "somefield": "value", 
    "items": [ 
     { LARGE OBJECT }, 
     { LARGE OBJECT }, 
     { LARGE OBJECT }, 
     { LARGE OBJECT }, 
     ... 
    ] 
} 

JSON將在某些時候包含一個大的任意長度的對象數組。我想將這些對象中的每一個都分別放入消息隊列中。我不需要自己解碼對象。

如果我使用了我的常規方法,它會在解碼之前將整個響應加載到內存中。

當響應仍在流入並將其派送到隊列中時,是否有很好的方法來分離出每個大對象項目?我這樣做是爲了避免在內存中保存儘可能多的數據。

謝謝!

+3

[請參閱文檔中的示例](https://godoc.org/encoding/json#example-Decoder-Decode-Stream)。 –

回答

4

json.Decoder可能解碼JSON流。我們可以讀取(解組)單個值而不消耗和解組整個流。這很酷,但是您的輸入是一個「單個」JSON對象,而不是一系列JSON對象,這意味着要調用Decoder.Decode()會試圖解組所有項目(大對象)的完整JSON對象。

我們想要的是部分地對單個JSON對象進行即時處理。爲此,我們可以使用Decoder.Token(),它僅解析(前進)JSON輸入流中的下一個後續標記並將其返回。這被稱爲事件驅動的解析。

當然,我們必須「處理」(解釋並採取行動)令牌並構建一個「狀態機」,以跟蹤我們處於我們正在處理的JSON結構中的位置。

這是一個解決您的問題的實現。

我們將使用以下JSON輸入:

{ 
    "somefield": "value", 
    "otherfield": "othervalue", 
    "items": [ 
     { "id": "1", "data": "data1" }, 
     { "id": "2", "data": "data2" }, 
     { "id": "3", "data": "data3" }, 
     { "id": "4", "data": "data4" } 
    ] 
} 

還要讀items的「大對象」的這種類型的建模:

type LargeObject struct { 
    Id string `json:"id"` 
    Data string `json:"data"` 
} 

我們還將分析和解釋在其他領域JSON對象,但我們只會記錄/打印它們。

爲了簡潔和便於錯誤處理,我們將使用這個助手的錯誤處理功能:

he := func(err error) { 
    if err != nil { 
     log.Fatal(err) 
    } 
} 

現在讓我們來看看一些行動。在下面的示例中,爲了簡潔起見,並在Go Playground上進行了演示,我們將從string的值中讀取值。要從實際HTTP響應正文閱讀,我們只有改變單一的線,這是我們如何創造json.Decoder

dec := json.NewDecoder(res.Body) 

所以演示:

dec := json.NewDecoder(strings.NewReader(jsonStream)) 
// We expect an object 
t, err := dec.Token() 
he(err) 
if delim, ok := t.(json.Delim); !ok || delim != '{' { 
    log.Fatal("Expected object") 
} 

// Read props 
for dec.More() { 
    t, err = dec.Token() 
    he(err) 
    prop := t.(string) 
    if t != "items" { 
     var v interface{} 
     he(dec.Decode(&v)) 
     log.Printf("Property '%s' = %v", prop, v) 
     continue 
    } 

    // It's the "items". We expect it to be an array 
    t, err := dec.Token() 
    he(err) 
    if delim, ok := t.(json.Delim); !ok || delim != '[' { 
     log.Fatal("Expected array") 
    } 
    // Read items (large objects) 
    for dec.More() { 
     // Read next item (large object) 
     lo := LargeObject{} 
     he(dec.Decode(&lo)) 
     fmt.Printf("Item: %+v\n", lo) 
    } 
    // Array closing delim 
    t, err = dec.Token() 
    he(err) 
    if delim, ok := t.(json.Delim); !ok || delim != ']' { 
     log.Fatal("Expected array closing") 
    } 
} 

// Object closing delim 
t, err = dec.Token() 
he(err) 
if delim, ok := t.(json.Delim); !ok || delim != '}' { 
    log.Fatal("Expected object closing") 
} 

這將產生以下輸出:

2009/11/10 23:00:00 Property 'somefield' = value 
2009/11/10 23:00:00 Property 'otherfield' = othervalue 
Item: {Id:1 Data:data1} 
Item: {Id:2 Data:data2} 
Item: {Id:3 Data:data3} 
Item: {Id:4 Data:data4} 

嘗試使用Go Playground的完整工作示例。

+0

謝謝@icza - 我認爲這正是我需要的! – JimBlizz