【问题标题】:Unmarshal a json stream (not newline-separated)解组 json 流(不是换行符分隔的)
【发布时间】:2015-07-10 18:50:17
【问题描述】:

我想将 JSON 流转换为对象流。使用换行符分隔的 JSON 很容易做到这一点。来自 Go 文档:https://golang.org/pkg/encoding/json/#Decoder.Buffered

但是,我需要从这样的 JSON 数组生成一个流:

        [{"Name": "Ed", "Text": "Knock knock."},
        {"Name": "Sam", "Text": "Who's there?"},
        {"Name": "Ed", "Text": "Go fmt."},
        {"Name": "Sam", "Text": "Go fmt who?"},
        {"Name": "Ed", "Text": "Go fmt yourself!"}]

什么是执行此操作的高效方法?

我考虑过这种方法:

  1. 去掉外面的尖括号
  2. 当存在匹配的顶级花括号时,解组大括号(包括)之间的字符串以一次获取一个顶级对象。

我不想这样做,因为两次扫描字符串的每个部分会影响性能。

我能做的最好的选择是复制 Golang encoding/json 包中解码器的源代码并对其进行修改,以便它返回一个每次输出一个对象的 Reader。但对于这么简单的要求来说,这似乎工作量太大了。

有没有更好的方法来解码 JSON 数组的流?

编辑

我希望解析带有嵌套对象和任意结构的 JSON。

【问题讨论】:

  • 是的,如果您知道格式是这样的,那么一次提取和解码对象的“预解析”对我来说听起来和任何方法一样好。这是一些可靠的示例文本。 :)
  • 示例文本来自 Go 文档——实际上直到现在才阅读!

标签: json go stream decode


【解决方案1】:

您可以使用流式解析器。例如megajson's scanner:

package main

import (
    "fmt"
    "strings"

    "github.com/benbjohnson/megajson/scanner"
)

func main() {
    // our incoming data
    rdr := strings.NewReader(`[
        {"Name": "Ed", "Text": "Knock knock."},
        {"Name": "Sam", "Text": "Who's there?"},
        {"Name": "Ed", "Text": "Go fmt."},
        {"Name": "Sam", "Text": "Go fmt who?"},
        {"Name": "Ed", "Text": "Go fmt yourself!"}
    ]`)

    // we want to create a list of these
    type Object struct {
        Name string
        Text string
    }
    objects := make([]Object, 0)

    // scan the JSON as we read
    s := scanner.NewScanner(rdr)

    // this is how we keep track of where we are parsing the JSON
    // if you needed to support nested objects you would need to
    // use a stack here ([]state{}) and push / pop each time you
    // see a brace
    var state struct {
        inKey   bool
        lastKey string
        object  Object
    }
    for {
        tok, data, err := s.Scan()
        if err != nil {
            break
        }

        switch tok {
        case scanner.TLBRACE:
            // just saw '{' so start a new object
            state.inKey = true
            state.lastKey = ""
            state.object = Object{}
        case scanner.TRBRACE:
            // just saw '}' so store the object
            objects = append(objects, state.object)
        case scanner.TSTRING:
            // for `key: value`, we just parsed 'key'
            if state.inKey {
                state.lastKey = string(data)
            } else {
                // now we are on `value`
                if state.lastKey == "Name" {
                    state.object.Name = string(data)
                } else {
                    state.object.Text = string(data)
                }
            }
            state.inKey = !state.inKey
        }
    }
    fmt.Println(objects)
}

这可能是您可以得到的最有效的方法,但它确实需要大量的手动处理。

【讨论】:

  • 这很好,但我正在寻找可以处理具有任意结构的嵌套对象的东西。如果我能弄清楚,我会更新问题并添加到您的代码中。
【解决方案2】:

假设 json 流如下:

{"Name": "Ed", "Text": "Knock knock."}{"Name": "Sam", "Text": "Who's there?"}{"Name": "Ed", "Text": "Go fmt."}

我有想法,伪代码如下:

1: skip prefix whitespace
2: if first char not {, throw error
3: load some chars, and find the first "}"
    4: if found, try json.Unmarshal()
        5: if unmarshal fail, load more chars, and find second "}"
             6: redo STEP 4

【讨论】:

  • 第 3 步很危险:load some chars, and find the first "}" 如果“}”在引号内,我们不想在那里停止扫描。示例:{"key": "value with }} brackets"}。您提供的伪代码用于一个简单的解析器,但需要的是一个成熟的解析器。
  • 是的,第 3 步很危险,但第 4/5/6 步将保证工作正常。
【解决方案3】:

下面是一个已经在我的项目中运行的实现:

package json

import (
    "bytes"
    j "encoding/json"
    "errors"
    "io"
    "strings"
)

// Stream represent a json stream
type Stream struct {
    stream *bytes.Buffer
    object *bytes.Buffer
    scrap  *bytes.Buffer
}

// NewStream return a Stream that based on src
func NewStream(src []byte) *Stream {
    return &Stream{
        stream: bytes.NewBuffer(src),
        object: new(bytes.Buffer),
        scrap:  new(bytes.Buffer),
    }
}

// Read read a json object
func (s *Stream) Read() ([]byte, error) {
    var obj []byte

    for {
        // read a rune from stream
        r, _, err := s.stream.ReadRune()
        switch err {
        case nil:
        case io.EOF:
            if strings.TrimSpace(s.object.String()) != "" {
                return nil, errors.New("Invalid JSON")
            }

            fallthrough
        default:
            return nil, err
        }

        // write the rune to object buffer
        if _, err := s.object.WriteRune(r); err != nil {
            return nil, err
        }

        if r == '}' {
            obj = s.object.Bytes()

            // check whether json string valid
            err := j.Compact(s.scrap, obj)
            s.scrap.Reset()
            if err != nil {
                continue
            }

            s.object.Reset()

            break
        }
    }

    return obj, nil
}

用法如下:

func process(src []byte) error {
    s := json.NewStream(src)

    for {
        obj, err := s.Read()
        switch err {
        case nil:
        case io.EOF:
            return nil 
        default:
            return err 
        }   

        // now you can try to decode the obj to a struct/map/...
        // it is also support mix stream, ex.:
        a = new(TypeOne)
        b = new(TypeTwo)
        if err := j.Unmarshal(obj, a); err == nil && a.Error != "" {
             // it is a TypeOne object
        } else if err := j.Unmarshal(obj, b); err == nil && a.ID != "" {
             // it is a TypeTwo object
        } else {
             // unkown type
        }
    }

    return nil
}

【讨论】:

    猜你喜欢
    • 2021-03-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-21
    • 1970-01-01
    • 1970-01-01
    • 2018-12-20
    相关资源
    最近更新 更多