json - 解码 JSON,因为它仍在通过 net/http 流入

标签 json http go streaming

过去,我使用 go 以如下所示的方式从 API 端点解码 JSON。

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 有时会包含一个大的、任意长度的对象数组。我想获取这些对象中的每一个并将它们分别放入消息队列中。我不需要解码对象本身。

如果我使用我的常规方法,这会在解码之前将整个响应加载到内存中。

在响应仍在流入并将其分派(dispatch)到队列时,是否有一种好的方法来拆分每个 LARGE OBJECT 项目?我这样做是为了避免在内存中保存太多数据。

最佳答案

使用 json.Decoder 可以解码 JSON 流.

Decoder.Decode() ,我们可以读取(解码)单个值而不使用和解码整个流。这很酷,但是您的输入是一个“单个”JSON 对象,而不是一系列 JSON 对象,这意味着调用 Decoder.Decode() 将尝试解码包含所有项目的完整 JSON 对象(大对象)。

我们想要的是对单个 JSON 对象的部分即时处理。为此,我们可以使用 Decoder.Token()它仅解析(推进)JSON 输入流中的下一个后续标记并返回它。这称为事件驱动解析。

当然,我们必须“处理”(解释和执行) token 并构建一个“状态机”来跟踪我们在我们正在处理的 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)
    }
}

现在让我们看看一些 Action 。在下面的示例中,为简洁起见并在 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 上尝试完整的工作示例.

关于json - 解码 JSON,因为它仍在通过 net/http 流入,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44307219/

相关文章:

java - Play 框架 2.3 中一种 Controller 方法的不同 URL 选择 View

google-chrome - Chrome 63 将 http 更改为 https

PHP header() 调用 "crashing"脚本出现 HTTP 500 错误

go - 尝试停止创建更多 goroutine 时出现 panic

java - 如何解析多部分/表单数据?

android - JSON Android 解析。卡在对话框中

java - 尝试在数据库中存储 Json 时出现 Ebean 'No service implementation found for SpiJsonService'

iOS - 由于 'val' 保护级别(来 self 的 pod), 'internal' 无法访问

XML-over-HTTP 分析/测试框架

google-app-engine - Go App Engine - 测试 Memcache 服务故障