2016-12-07 35 views
1

我正在读取并解码其中有错误的大型JSON响应。现在我需要找到其中的错误是!我read about json.SyntaxError但我努力找出如何使用它。从Golang调试JSON错误

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) 
    } 

} 

我在想什么?任何工具或策略或建议将不胜感激。我的输出目前的样子:

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 

但我需要这样的事情在我的错误输出到更好的调试问题

Line 8: published_at is invalid 

等的标题也许有些情况下,所以我可以告诉API后端团队在他们的JSON响应中出现错误。

奖金问题:而且我不希望打印的价值0001-01-01 00:00:00 +0000 UTC因为它居然真的空。我并不介意它错过了。

+0

错误消息确切地告诉错误在哪里。 *解析时间“”null“”as“”2006-01-02T15:04:05Z07:00“”:无法解析“null”为“2006”*。在您的JSON正文中,您已将null键入为字符串(“null”)。尝试删除引号。 – Nadh

+0

我知道有一个错误,因为我把它放在那里。我的问题是问如何打印错误发生的地方。 – hendry

+1

你试过'json.unmarshal() '函数?如果有任何可以帮助你的函数,它会返回错误,请参见[https://play.golang.org/p/eQCG-RE5sK](https://play.golang.org/p/eQCG-RE5sK ) – tgogos

回答

2

一种方法既接受空值,如果published_at是空不打印任何东西,是PublishedAt字段设置为指针值:

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

输入字符串是有效JSON ,所以json包不会引发SyntaxError

json封装具有一些其他错误类型,如UnmarshalTypeError时,当JSON不匹配nuilt入型中发生错误,其被升高(例如:stringintarray ...)。

不幸的是,当它调用自定义UnmarshalJSON()功能,它看起来像json包返回原始错误:

package main 

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

// check the full type of an error raised when Unmarshaling a json string 
func main() { 
    var test struct { 
     Clock time.Time 
    } 
    buf := bytes.NewBufferString(`{"Clock":null}`) 
    dec := json.NewDecoder(buf) 

    // ask to decode an invalid null value into a flat time.Time field : 
    err := dec.Decode(&test) 

    // print the details of the returned error : 
    fmt.Printf("%#v\n", err) 
} 

// Output : 
&time.ParseError{Layout:"\"2006-01-02T15:04:05Z07:00\"", Value:"null", LayoutElem:"\"", ValueElem:"null", Message:""} 

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

最终的误差从time包中附带直,不json软件包中的某种UnmarshalError,它至少可以告诉您“尝试以此偏移量取值时出现此错误”,并且错误本身不会为您提供上下文。


你可以在错误特意找*time.ParseError类型:

if terr, ok := err.(*time.ParseError); ok { 
    // in the example : Movie has one single time.Time field ; 
    // if a time.ParseError occured, it was while trying to read that field 
    fmt.Println("Error when trying to read 'published_at' value", terr) 

    // you can leave the field to its zero value, 
    // or if you switched to a pointer field : 
    m.PublishedAt = nil 
} 

如果你碰巧有几个时间字段(如G:ProducedAtPublishedAt),你仍然可以期待哪场留下了它的零值:

if terr, ok := err.(*time.ParseError); ok { 
    if m.ProducedAt.IsZero() { 
     fmt.Println("Error when trying to read 'produced_at' value", terr) 
    } 

    if m.PublishedAt == zero { 
     fmt.Println("Error when trying to read 'published_at' value", terr) 
    } 
} 

顺便说一句:在the docs规定,“0001-01-01 00:00:00 UTC”是去团队选择的零值为time.Time零值。

+0

我很快尝试* time.Time在我的结构中,但我仍然有“OUTPUT:barbar被发布在0001-01-01 00:00:00 +0000 UTC” – hendry

+0

啊,它看起来像解析器试图解码* string *值'“ null“'而不是普通的* null *值(不带引号的'null')。 – LeGEC

+0

顺便说一句,有https://golang.org/pkg/time/#Time.IsZero ......但它确实吸引了如何通过行号标记原始JSON中的问题并不容易。 – hendry

0

您的published_at数据为“null”,它是字符串类型,所以我认为您可以将PublishedAt定义为字符串,并且可以使用代码将其解析为time.Time。

这是我的测试代码:

package main 

import (
    "encoding/json" 

    "github.com/swanwish/go-common/logs" 
    "github.com/swanwish/go-common/utils" 
) 

func main() { 
    url := `https://s.natalian.org/2016-12-07/debugme2.json` 
    _, content, err := utils.GetUrlContent(url) 
    if err != nil { 
     logs.Errorf("Failed to get content from url %s, the error is %v", url, err) 
     return 
    } 

    movies := []struct { 
     Title  string `json:"title"` 
     PublishedAt string `json:"published_at"` 
    }{} 
    err = json.Unmarshal(content, &movies) 
    if err != nil { 
     logs.Errorf("Failed to unmarshal content %s, the error is %v", string(content), err) 
     return 
    } 
    logs.Debugf("The movies are %v", movies) 
} 

结果是:

The movies are [{foobar 2016-11-24T16:17:12.000+08:00} {barbar null} { 1999-12-24T16:11:12.000+02:00} {Something else entirely 2000-01-24T16:11:12.000+02:00}] 
+0

我宁愿试着解析它(确定错误)并完成它。什么是不必要的第三方工具?但它可能不是一个字符串。可能只是空。 – hendry

0

它看起来像疯狂,但它应该工作:

rawBody := []byte(`{"title":"test", "published_at":"2017-08-05T15:04:05Z", "edited_at":"05.08.2017"}`) 

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

var msg Movie 

if err = json.Unmarshal(rawBody, &msg); err != nil { 
    if _, ok := err.(*time.ParseError); ok { 
     value := reflect.ValueOf(msg).Elem() 

     if value.Kind().String() != "struct" { 
      return err 
     } 

     for i := 0; i < value.NumField(); i++ { 
      field := value.Field(i) 

      if t, ok := field.Interface().(time.Time); ok { 
       if t.IsZero() { 
        name := value.Type().Field(i).Name 
        return fmt.Errorf("field: %s, message: %s", strings.ToLower(name), "time is not in RFC 3339 format.") 
       } 
      } 
     } 
    } 

    return err 
} 

此代码将先返回发生错误。如果PublishedAt无效,即使它是有效的,我们也不会对EditedA一无所知。