如何跳过' noise'在JSON对象流中?

时间:2017-11-02 21:26:38

标签: json parsing go io

尝试获取以下代码以跳过JSON数据对象流中的解析错误 noise 。基本上我希望它跳过ERROR: ...行并继续下一个可解析的记录。

json.Decoder有一组有限的methods - 所以它不清楚如何向前移动解码器的索引(比如一次一个字节)以超越噪声

io.Reader有方法可以跳过来说明行的结尾(或者至少尝试跳过一个字符) - 但这样的操作不会(理解)影响json.Decoder&#39寻求国家。

有干净的方法吗?

https://play.golang.org/p/riIDh9g1Rx

package main

import (
        "encoding/json"
        "fmt"
        "strings"
        "time"
)

type event struct {
        T    time.Time
        Desc string
}

var jsonStream = ` 
{"T":"2017-11-02T16:00:00-04:00","Desc":"window opened"}
{"T":"2017-11-02T16:30:00-04:00","Desc":"window closed"}
{"T":"2017-11-02T16:41:34-04:00","Desc":"front door opened"}
ERROR: retrieving event 1234
{"T":"2017-11-02T16:41:40-04:00","Desc":"front door closed"}
`

func main() {
        jsonReader := strings.NewReader(jsonStream)
        decodeStream := json.NewDecoder(jsonReader)

        i := 0
        for decodeStream.More() {
                i++ 
                var ev event
                if err := decodeStream.Decode(&ev); err != nil {
                        fmt.Println("parse error: %s", err)
                        break
                }   
                fmt.Printf("%3d: %+v\n", i, ev) 
        }   
}

得到:

  1: {T:2017-11-02 16:00:00 -0400 -0400 Desc:window opened}
  2: {T:2017-11-02 16:30:00 -0400 -0400 Desc:window closed}
  3: {T:2017-11-02 16:41:34 -0400 -0400 Desc:front door opened}
parse error: %s invalid character 'E' looking for beginning of value

想:

  1: {T:2017-11-02 16:00:00 -0400 -0400 Desc:window opened}
  2: {T:2017-11-02 16:30:00 -0400 -0400 Desc:window closed}
  3: {T:2017-11-02 16:41:34 -0400 -0400 Desc:front door opened}
  4: {T:2017-11-02 16:41:40 -0400 -0400 Desc:front door closed}

2 个答案:

答案 0 :(得分:5)

我认为"正确"方法这样做,因为流本身是无效的JSON (即使没有错误,JSON文档必须有一个根条目,这是一系列无效的根对象),要预先解析为单独的,有效的JSON文档,并单独解组。使用例如逐行读取流bufio.Scanner,丢弃非JSON行,Unmarshal其他行正常。

请参阅此处的工作示例:https://play.golang.org/p/DZrAVmzwr-

答案 1 :(得分:1)

虽然不是很干净,但您可以使用JSON解码器的Buffered方法来访问底层读取器,它仍应指向导致错误的字节,并将其包装在缓冲读取器中必要时。然后,您可以读取单个字节,直到遇到有效的JSON起始对象字节{并且未读取该字节(在任何实现中至少1个字节可以未读取)以将字节推回到缓冲流上。

Playground link for code below

...
decodeLoop:
    for decodeStream.More() {
        i++
        var ev event
        if err := decodeStream.Decode(&ev); err != nil {
            r := decodeStream.Buffered()
            br, ok := r.(*bufio.Reader)
            if !ok {
                br = bufio.NewReader(r)
            }
            for {
                b, err := br.ReadByte()
                if err != nil {
                    // Whether EOF or not, there's nothing left to do except
                    // break the loop to trigger the "parse error" statement.
                    break
                }
                // A (potentially) valid JSON object was found;
                // create a new decoder associated with the same decodeStream var
                // using the new buffered reader and continue decoding.
                if b == '{' {
                    br.UnreadByte()
                    decodeStream = json.NewDecoder(br)
                    continue decodeLoop
                }
            }
            fmt.Println("parse error: %s", err)
            break
        }
        ...

然而,this is not bulletproof as-is

恕我直言,处理此问题的正确方法要求您收到一个JSON对象的JSON数组,允许您通过提供event方法来处理代表UnmarshalJSON的每个JSON对象的手动标记化使用*event方法接收器,但是如果你不能得到它,那么这并不重要,并且你需要修改提供的解决方案以使其在必要时工作,假设这是可能的。一种可能的补救方法是设置一个标志,并在检测到有效的JSON对象时取消设置:

    objectDetected := false
    i := 0
decodeLoop:
    ...
                if b == '{' {
                    // If we already encountered an object and found ourselves here again,
                    // it's not really a valid JSON object.
                    if objectDetected {
                        break
                    }
                    objectDetected = true
                    br.UnreadByte()
                    ...
        fmt.Printf("%3d: %+v\n", i, ev)
        objectDetected = false
    } // decode loop end
}

Playground link