/ / Depurando um erro JSON de Golang - json, vá

Depurando um erro JSON do Golang - json, go

Estou buscando e decodificando uma grande resposta JSON que contém um erro. Agora preciso encontrar Onde o erro é! eu leia sobre json.SyntaxError mas estou lutando para descobrir como usá-lo.

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

}

o que estou perdendo? Quaisquer ferramentas ou estratégias ou sugestões seriam muito apreciadas. Minha saída atualmente se parece com:

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

Mas eu preciso de algo assim no meu stderr para melhor depurar o problema:

Line 8: published_at is invalid

E talvez algum contexto do Título para que eu possa dizer à equipe de back-end da API que eles têm um erro em sua resposta JSON.

Pergunta BÔNUS: Além disso, não quero imprimir o valor 0001-01-01 00:00:00 +0000 UTC porque está realmente vazio. Na verdade, não me importo que esteja faltando.

Respostas:

2 para resposta № 1

Uma maneira de aceitar valores nulos e de não imprimir nada se published_at é nulo, é para definir PublishedAt campo para um valor de ponteiro:

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

A string de entrada é válida JSON, então o pacote json não levanta um SyntaxError.

o json pacote tem alguns outros tipos de erro, como UnmarshalTypeError, que é gerado quando ocorre um erro quando o json não corresponde a um tipo nuilt-in (por exemplo: string, int, array ...)

Infelizmente, quando chama um costume UnmarshalJSON() função, parece que o json pacote retorna o erro bruto:

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("%#vn", err)
}

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

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

O erro final vem direto do time pacote, não é algum tipo de UnmarshalError de json pacote que poderia pelo menos dizer "este erro ocorreu ao tentar descompactar o valor neste deslocamento", e o erro sozinho não fornecerá o contexto.


Você pode procurar especificamente pelo tipo *time.ParseError no erro:

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
}

Se acontecer de você ter vários campos de tempo (por exemplo: ProducedAt e PublishedAt), você ainda pode ver qual campo foi deixado com seu valor zero:

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

A propósito: conforme especificado em os docs, "0001-01-01 00:00:00 UTC" é o valor zero que a equipe go escolheu para go "s time.Time valor zero.


0 para resposta № 2

Seus dados para posted_at são "nulos", são do tipo string, então acho que você pode definir PublishedAt como string e pode usar o código para analisá-lo para time.Time.

Este é o meu código de teste:

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

O resultado é:

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 para resposta № 3

Parece uma loucura, mas deve funcionar:

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
}

Este código retornará o primeiro erro ocorrido. Se PublishedAt for inválido, não saberemos nada sobre EditedAt, mesmo que seja válido.