如何在不解码的情况下读取 Go 中的 JSON 对象(用于读取大流)
How to read a JSON object in Go without decoding it (for use in reading a large stream)
我正在阅读 JSON 以响应 HTTP 端点,并想提取嵌套在其中的对象数组的内容。响应可能很大,所以我正在尝试使用流式传输方法,而不是 json.Unmarshal 整个过程。 JSON 看起来像这样:
{
"useless_thing_1": { /* etc */ },
"useless_thing_2": { /* etc */ },
"the_things_i_want": [
{ /* complex object I want to json.Unmarshal #1 */ },
{ /* complex object I want to json.Unmarshal #2 */ },
{ /* complex object I want to json.Unmarshal #3 */ },
/* could be many thousands of these */
],
"useless_thing_3": { /* etc */ },
}
Go 提供的 json 库有 json.Unmarshal
可以很好地处理完整的 JSON 对象。它还具有 json.Decoder
可以解组完整对象或提供单个标记。我可以使用这个分词器来仔细检查和提取东西,但这样做的逻辑有点复杂,在我将它作为标记读取后,我不能再轻易地在对象上使用 json.Unmarshal
。
json.Decoder 是缓冲的,这使得很难读取一个对象(即 { /* complex object I want to json.Unmarshal #1 */ }
),然后自己消耗 ,
并创建一个新的 json.Decoder
- 因为它会尝试消耗逗号本身。这是我尝试过但未能奏效的方法。
我正在寻找解决此问题的更好方法。这是我尝试手动使用逗号时损坏的代码:
// code here that naively looks for `"the_things_i_want": [` and
// puts the next bytes after that in `buffer`
// this is the rest of the stream starting from `{ /* complex object I want to json.Unmarshal #1 */ },`
in := io.MultiReader(buffer, res.Body)
dec := json.NewDecoder(in)
for {
var p MyComplexThing
err := dec.Decode(&p)
if err != nil {
panic(err)
}
// steal the comma from in directly - this does not work because the decoder buffer's its input
var b1 [1]byte
_, err = io.ReadAtLeast(in, b1[:], 1) // returns random data from later in the stream
if err != nil {
panic(err)
}
switch b1[0] {
case ',':
// skip over it
case ']':
break // we're done
default:
panic(fmt.Errorf("Unexpected result from read %#v", b1))
}
}
使用 Decoder.Token and Decoder.More 将 JSON 文档解码为流。
浏览文档,Decoder.Token to the JSON value of interest. Call Decoder.Decode 将 JSON 值解组为 Go 值。根据需要重复以吸收所有感兴趣的值。
下面是一些带有注释的代码,解释了它是如何工作的:
func decode(r io.Reader) error {
d := json.NewDecoder(r)
// We expect that the JSON document is an object.
if err := expect(d, json.Delim('{')); err != nil {
return err
}
// While there are fields in the object...
for d.More() {
// Get field name
t, err := d.Token()
if err != nil {
return err
}
// Skip value if not the field that we are looking for.
if t != "the_things_i_want" {
if err := skip(d); err != nil {
return err
}
continue
}
// We expect JSON array value for the field.
if err := expect(d, json.Delim('[')); err != nil {
return err
}
// While there are more JSON array elements...
for d.More() {
// Unmarshal and process the array element.
var m map[string]interface{}
if err := d.Decode(&m); err != nil {
return err
}
fmt.Printf("found %v\n", m)
}
// We are done decoding the array.
return nil
}
return errors.New("things I want not found")
}
// skip skips the next value in the JSON document.
func skip(d *json.Decoder) error {
n := 0
for {
t, err := d.Token()
if err != nil {
return err
}
switch t {
case json.Delim('['), json.Delim('{'):
n++
case json.Delim(']'), json.Delim('}'):
n--
}
if n == 0 {
return nil
}
}
}
// expect returns an error if the next token in the document is not expectedT.
func expect(d *json.Decoder, expectedT interface{}) error {
t, err := d.Token()
if err != nil {
return err
}
if t != expectedT {
return fmt.Errorf("got token %v, want token %v", t, expectedT)
}
return nil
}
我正在阅读 JSON 以响应 HTTP 端点,并想提取嵌套在其中的对象数组的内容。响应可能很大,所以我正在尝试使用流式传输方法,而不是 json.Unmarshal 整个过程。 JSON 看起来像这样:
{
"useless_thing_1": { /* etc */ },
"useless_thing_2": { /* etc */ },
"the_things_i_want": [
{ /* complex object I want to json.Unmarshal #1 */ },
{ /* complex object I want to json.Unmarshal #2 */ },
{ /* complex object I want to json.Unmarshal #3 */ },
/* could be many thousands of these */
],
"useless_thing_3": { /* etc */ },
}
Go 提供的 json 库有 json.Unmarshal
可以很好地处理完整的 JSON 对象。它还具有 json.Decoder
可以解组完整对象或提供单个标记。我可以使用这个分词器来仔细检查和提取东西,但这样做的逻辑有点复杂,在我将它作为标记读取后,我不能再轻易地在对象上使用 json.Unmarshal
。
json.Decoder 是缓冲的,这使得很难读取一个对象(即 { /* complex object I want to json.Unmarshal #1 */ }
),然后自己消耗 ,
并创建一个新的 json.Decoder
- 因为它会尝试消耗逗号本身。这是我尝试过但未能奏效的方法。
我正在寻找解决此问题的更好方法。这是我尝试手动使用逗号时损坏的代码:
// code here that naively looks for `"the_things_i_want": [` and
// puts the next bytes after that in `buffer`
// this is the rest of the stream starting from `{ /* complex object I want to json.Unmarshal #1 */ },`
in := io.MultiReader(buffer, res.Body)
dec := json.NewDecoder(in)
for {
var p MyComplexThing
err := dec.Decode(&p)
if err != nil {
panic(err)
}
// steal the comma from in directly - this does not work because the decoder buffer's its input
var b1 [1]byte
_, err = io.ReadAtLeast(in, b1[:], 1) // returns random data from later in the stream
if err != nil {
panic(err)
}
switch b1[0] {
case ',':
// skip over it
case ']':
break // we're done
default:
panic(fmt.Errorf("Unexpected result from read %#v", b1))
}
}
使用 Decoder.Token and Decoder.More 将 JSON 文档解码为流。
浏览文档,Decoder.Token to the JSON value of interest. Call Decoder.Decode 将 JSON 值解组为 Go 值。根据需要重复以吸收所有感兴趣的值。
下面是一些带有注释的代码,解释了它是如何工作的:
func decode(r io.Reader) error {
d := json.NewDecoder(r)
// We expect that the JSON document is an object.
if err := expect(d, json.Delim('{')); err != nil {
return err
}
// While there are fields in the object...
for d.More() {
// Get field name
t, err := d.Token()
if err != nil {
return err
}
// Skip value if not the field that we are looking for.
if t != "the_things_i_want" {
if err := skip(d); err != nil {
return err
}
continue
}
// We expect JSON array value for the field.
if err := expect(d, json.Delim('[')); err != nil {
return err
}
// While there are more JSON array elements...
for d.More() {
// Unmarshal and process the array element.
var m map[string]interface{}
if err := d.Decode(&m); err != nil {
return err
}
fmt.Printf("found %v\n", m)
}
// We are done decoding the array.
return nil
}
return errors.New("things I want not found")
}
// skip skips the next value in the JSON document.
func skip(d *json.Decoder) error {
n := 0
for {
t, err := d.Token()
if err != nil {
return err
}
switch t {
case json.Delim('['), json.Delim('{'):
n++
case json.Delim(']'), json.Delim('}'):
n--
}
if n == 0 {
return nil
}
}
}
// expect returns an error if the next token in the document is not expectedT.
func expect(d *json.Decoder, expectedT interface{}) error {
t, err := d.Token()
if err != nil {
return err
}
if t != expectedT {
return fmt.Errorf("got token %v, want token %v", t, expectedT)
}
return nil
}