从 Golang 调试 JSON 错误

hen*_*dry 7 json go

我正在获取并解码一个包含错误的大型 JSON 响应。现在我需要找出错误在哪里!读到了有关 json.SyntaxError 的内容,但我正在努力找出如何使用它。

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "os"
    "text/template"
    "time"
)

type Movie struct {
    Title       string    `json:"title"`
    PublishedAt time.Time `json:"published_at"`
}

func main() {
    req, _ := http.NewRequest("GET", "https://s.natalian.org/2016-12-07/debugme2.json", nil)
    resp, err := http.DefaultClient.Do(req)

    defer resp.Body.Close()
    dec := json.NewDecoder(resp.Body)

    _, err = dec.Token()
    for dec.More() {
        var m Movie
        if err = dec.Decode(&m); err != nil {
            fmt.Println(err)
            fmt.Println("Bad", m)

            // https://blog.golang.org/error-handling-and-go
            if serr, ok := err.(*json.SyntaxError); ok {
                fmt.Println("Syntax error", serr)
            }

        } else {
            fmt.Println("Good", m)
        }

        tmpl := template.Must(template.New("test").Parse("OUTPUT: {{ if .Title }}{{.Title}}{{ if .PublishedAt }} was published at {{.PublishedAt}} {{ end }}{{end}}\n"))
        tmpl.Execute(os.Stdout, m)
    }

}
Run Code Online (Sandbox Code Playgroud)

我缺少什么?任何工具、策略或建议将不胜感激。我的输出目前看起来像:

Good {foobar 2016-11-24 16:17:12 +0800 SGT}
OUTPUT: foobar was published at 2016-11-24 16:17:12 +0800 SGT
parsing time ""null"" as ""2006-01-02T15:04:05Z07:00"": cannot parse "null"" as "2006"
Bad {barbar 0001-01-01 00:00:00 +0000 UTC}
OUTPUT: barbar was published at 0001-01-01 00:00:00 +0000 UTC
Good { 1999-12-24 16:11:12 +0200 +0200}
OUTPUT:
Good {Something else entirely 2000-01-24 16:11:12 +0200 +0200}
OUTPUT: Something else entirely was published at 2000-01-24 16:11:12 +0200 +0200
Run Code Online (Sandbox Code Playgroud)

但我的 stderr 中需要类似的东西来更好地调试问题

Line 8: published_at is invalid
Run Code Online (Sandbox Code Playgroud)

也许还有标题的一些上下文,这样我就可以告诉 API 后端团队他们的 JSON 响应中有错误。

额外问题:此外,我不想打印值0001-01-01 00:00:00 +0000 UTC,因为它实际上是空的。我其实并不介意它丢失。

Pav*_*eev 5

我找到了一些解决方案:

if err := json.Unmarshal([]byte(data), &myStruct); err != nil {
    if jsonErr, ok := err.(*json.SyntaxError); ok {
        problemPart := data[jsonErr.Offset-10 : jsonErr.Offset+10]
        err = fmt.Errorf("%w ~ error near '%s' (offset %d)", err, problemPart, jsonErr.Offset)
    }
}
Run Code Online (Sandbox Code Playgroud)

它会打印类似的东西

invalid character 'n' after object key:value pair ~ error near 'rence\","numberOfBil' (offset 14557)
Run Code Online (Sandbox Code Playgroud)


LeG*_*GEC 4

既接受空值又不打印任何内容(如果published_at为空)的一种方法是将PublishedAt字段设置为指针值:

\n\n
type Movie struct {\n    Title       string    `json:"title"`\n    PublishedAt *time.Time `json:"published_at"`\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n\n

输入字符串是有效的JSON,因此 json 包不会引发SyntaxError.

\n\n

json包还有一些其他错误类型,例如UnmarshalTypeError,当 json 与 nuilt-in 类型不匹配(例如 : string, int, array...)时发生错误时会引发该错误类型。

\n\n

不幸的是,当它调用自定义UnmarshalJSON()函数时,包看起来json会返回原始错误:

\n\n
package main\n\nimport (\n    "bytes"\n    "encoding/json"\n    "fmt"\n    "time"\n)\n\n// check the full type of an error raised when Unmarshaling a json string\nfunc main() {\n    var test struct {\n        Clock time.Time\n    }\n    buf := bytes.NewBufferString(`{"Clock":null}`)\n    dec := json.NewDecoder(buf)\n\n    // ask to decode an invalid null value into a flat time.Time field :\n    err := dec.Decode(&test)\n\n    // print the details of the returned error :\n    fmt.Printf("%#v\\n", err)\n}\n\n// Output :\n&time.ParseError{Layout:"\\"2006-01-02T15:04:05Z07:00\\"", Value:"null", LayoutElem:"\\"", ValueElem:"null", Message:""}\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n

最终的错误直接来自time包,而不是某种UnmarshalError来自json,它至少可以告诉您“尝试在这个偏移量解组值时发生此错误”,并且该错误本身不会为您提供上下文。

\n\n
\n\n

你可以专门找类型*time.ParseError错误中的类型:

\n\n
if terr, ok := err.(*time.ParseError); ok {\n    // in the example : Movie has one single time.Time field ;\n    // if a time.ParseError occured, it was while trying to read that field\n    fmt.Println("Error when trying to read \'published_at\' value", terr)\n\n    // you can leave the field to its zero value,\n    // or if you switched to a pointer field :\n    m.PublishedAt = nil\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

如果您碰巧有多个时间字段(例如:ProducedAt\xc2\xa0andPublishedAt),您仍然可以查看哪个字段的值为零:

\n\n
if terr, ok := err.(*time.ParseError); ok {\n    if m.ProducedAt.IsZero() {\n        fmt.Println("Error when trying to read \'produced_at\' value", terr)\n    }\n\n    if m.PublishedAt == zero {\n        fmt.Println("Error when trying to read \'published_at\' value", terr)\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

顺便说一下:正如文档中所指定的,“0001-01-01 00:00:00 UTC”是 go 团队为 go 选择的零值time.Time零值选择的零值。

\n