Как читать объект JSON в Go без его декодирования (для использования при чтении большого потока) - PullRequest
0 голосов
/ 15 апреля 2020

Я читаю JSON в ответ на конечную точку HTTP и хотел бы извлечь содержимое массива объектов, вложенных внутрь. Ответ может быть большим, поэтому я пытаюсь использовать потоковый подход, а не просто json. Уничтожение всего этого. 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 */ },
}

Библиотека json, поставляемая с Go, имеет json.Unmarshal, которая хорошо работает для полных JSON объектов. Он также имеет json.Decoder, который может демаршировать полные объекты или предоставлять отдельные жетоны. Я могу использовать этот токенизатор для тщательного go прохождения и извлечения чего-либо, но логика c для этого несколько сложна, и я не могу тогда легко использовать 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))
    }
}

1 Ответ

1 голос
/ 15 апреля 2020

Используйте Decoder.Token и Decoder.More для декодирования JSON документа в виде потока.

Просмотр документа с помощью Decoder. Токен на JSON интересующего значения. Вызовите 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
}

Запустите его на игровой площадке .

...