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 № 1Uma 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.