2016-12-07 34 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一無所知。