Debugging a JSON error from Golang - json

I'm fetching and decoding a large JSON response that has an error in it. Now I need to find where the error is! I read about json.SyntaxError but I am struggling to find out how to use it.
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)
}
}
What am I missing? Any tools or strategies or suggestions would be much appreciated. My output currently looks like:
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
But I need something like this in my stderr to better debug the issue:
Line 8: published_at is invalid
And maybe some context of the Title so I can tell the API backend team they have an error in their JSON response.
BONUS question: Furthermore I don't want to print the value 0001-01-01 00:00:00 +0000 UTC as it's actually really empty. I don't actually mind it being missing.

I found some solution:
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)
}
}
It will print something like
invalid character 'n' after object key:value pair ~ error near 'rence\","numberOfBil' (offset 14557)

One way to both accept null values, and to not print anything if published_at is null, is to set PublishedAt field to a pointer value :
type Movie struct {
Title string `json:"title"`
PublishedAt *time.Time `json:"published_at"`
}
The input string is valid JSON, so the json package does not raise a SyntaxError.
The json package has some other error types, such as UnmarshalTypeError, which is raised when an error occurs when the json does not match a nuilt-in type (e.g : string, int, array ...).
Unfortunately, when it calls a custom UnmarshalJSON() function, it looks like the json package returns the raw error :
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
The final error comes straight from the time package, it is not some kind of UnmarshalError from the json package which could at least tell you "this error occured when trying to Unmarshal value at this offset", and the error alone will not give you the context.
You can look specifically for type *time.ParseError in the error :
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
}
If you happen to have several time fields (e.g : ProducedAt and PublishedAt), you can still look which field was left with its zero value :
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)
}
}
By the way : as specified in the docs, "0001-01-01 00:00:00 UTC" is the zero value that the go team chose for go's time.Time zero value.

Your data for published_at is "null", it is string type, so I think you can define the PublishedAt as string, and you can use code to parse it to time.Time.
This is my test code:
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 result is:
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}]

It looks like madness, but it should work:
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
}
This code will return first error happened. If PublishedAt is invalid we will know nothing about EditedAt even if it is valid.

Related

How to write unit test failure for json.NewDecoder.Decode?

I have to write unit tests for a function and this function uses json.NewDecoder.Decode
var infos models.RegisterInfos // struct with json fields
err := json.NewDecoder(r.Body).Decode(&infos)
if err != nil {
// do something
}
How can I simulate an error in a unit test (using the testing package) for json.NewDecoder(r.Body).Decode(&infos) ? I tried looking in the NewDecoder and Decode source code but I couldn't find anything that can generate an error in just a few lines.
you could send a body like <invalid json> as example:
func main() {
body := "<invalid json>"
var infos RegisterInfos // struct with json fields
err := json.NewDecoder(strings.NewReader(body)).Decode(&infos)
if err != nil {
fmt.Println(err)
}
}
See https://go.dev/play/p/44E99D0eQou
Feed it an invalid input, or decode into an invalid output:
package main
import (
"encoding/json"
"fmt"
"strings"
"testing"
)
type Message struct {
Name string
}
func TestDecodeFail(t *testing.T) {
for _, tc := range []struct {
in string
desc string
out any
}{
{`{Name: "Bobby"}`, "key without quotes", &Message{}},
{`{"Name": "Foo"a}`, "extra character", &Message{}},
{`{"Name": "Foo"}`, "bad destination", &struct{ Name int64 }{}},
{`{"Name": "Foo` + "\u001a" + `"}`, "invalid character", &Message{}},
{`{"Name": "Foo"}`, "unmarshal to nil", (*Message)(nil)},
} {
err := decode(tc.in, tc.out)
if err != nil {
fmt.Printf("%s -> %s, %T\n", tc.desc, err.Error(), err)
}
}
}
func decode(in string, out any) error {
return json.NewDecoder(strings.NewReader(in)).Decode(out)
}
Outputs:
key without quotes -> invalid character 'N' looking for beginning of object key string, *json.SyntaxError
extra character -> invalid character 'a' after object key:value pair, *json.SyntaxError
bad destination -> json: cannot unmarshal string into Go struct field .Name of type int64, *json.UnmarshalTypeError
invalid character -> invalid character '\x1a' in string literal, *json.SyntaxError
unmarshal to nil -> json: Unmarshal(nil *main.Message), *json.InvalidUnmarshalError

Parse Error from Converting Json String to Struct

i cannot parse the json value i am sending a playground link
Any idea about that? here is the link and codes
https://play.golang.org/p/qhZpS_-618s
package main
import (
"encoding/json"
"fmt"
//mapstructure "github.com/mitchellh/mapstructure"
)
type presence struct{
id string
m_type string
deny string
}
type jsonHandler struct {
name string
dat map[string]interface{}
}
func main() {
s := `["Presence",{"id":"905356870666#c.us","type":"unavailable","deny":true}]`
data := jsonHandler{}
json.Unmarshal([]byte(s), &data)
fmt.Printf("Operation: %s", data.name)
}
Output :
Operation:
Program exited.
Try with this one: https://play.golang.com/p/UICf_uNNFdC
I've commented a lot in order to enhance code readability. Be sure to handle error properly and remove debug print.
package main
import (
"encoding/json"
"log"
"strings"
)
type Presence struct {
Presence string
ID string `json:"id"`
Type string `json:"type"`
Deny bool `json:"deny"`
}
type JsonHandler struct {
Name string `json:"name"`
Dat Presence `json:"dat"`
}
func main() {
var (
// Used for unmarshal a given json
packedData []json.RawMessage
err error
// Data that does not have a related json key
name []byte
// Used for extract the raw data that will be unmarshalled into the Presence struct
temp []byte
// Nested json
jsonPresence Presence
handler JsonHandler
)
s := `["Presence",{"id":"905356870666#c.us","type":"unavailable","deny":true}]`
log.Println("Dealing with -> " + s)
// Unmarshall into a raw json message
err = json.Unmarshal([]byte(s), &packedData)
if err != nil {
panic(err)
}
// Extract the presence
log.Println("Presence: ", string(packedData[0]))
// Extract the nested json
log.Println("Packed: ", string(packedData[1]))
// NOTE: 0 refers to the first value of the JSON
name, err = packedData[0].MarshalJSON()
if err != nil {
panic(err)
}
log.Println("Value that does not have a key: " + string(name))
handler.Name = strings.Replace(string(name), "\"", "", -1)
// NOTE: 1 refers to the second value of the JSON, the entire JSON
// Unmarshal the nested Json into byte
temp, err = packedData[1].MarshalJSON()
if err != nil {
panic(err)
}
// Unmarshal the raw byte into the struct
err = json.Unmarshal(temp, &jsonPresence)
if err != nil {
panic(err)
}
log.Println("ID:", jsonPresence.ID)
log.Println("Type:", jsonPresence.Type)
log.Println("Deny:", jsonPresence.Deny)
handler.Dat = jsonPresence
log.Println("Data unmarshalled: ", handler)
}
Go Playground Link: https://play.golang.org/p/qe0jyFVNTH1
Few Problem are present in this:
1. Json Package can't refer the Unexported Structure Elements.So please use Deny instead of deny in the following snippet.This is applicable to all variables declared inside the structure
2. The json fields tag are incorrect. eg.mapstructure:"id" should be json:"id"
3. The json to be parsed contains two distinct elements i.e string "Presence" and nested json object.It can't be parsed as a single element.It is better to declare "Presence" as a key and nested json as the value.
4. The deny variable should be bool rather than string
Wow,solved problem by adding only these codes
Here Go Lang Link : https://play.golang.org/p/doHNWK58Cae
func (n *JsonHandler) UnmarshalJSON(buf []byte) error {
tmp := []interface{}{&n.Name, &n.Dat}
wantLen := len(tmp)
if err := json.Unmarshal(buf, &tmp); err != nil {
return err
}
if g, e := len(tmp), wantLen; g != e {
return fmt.Errorf("wrong number of fields in Notification: %d != %d", g, e)
}
return nil
}

GOCSV mapping to struct with Date/Time: Issue when the field is empty

EDIT: I Made a mistake and corrected it
I'm trying to map a CSV file to a struct that defines fields of time.Datetime (later I want to change the datetime format to another format).
This is the output of my program
❯ go run issue.go ✗ master
record: 05/02/2019 15:02:31
record: 05/02/2019 16:02:40
record:
record on line 0; parse error on line 4, column 2: parsing time "" as "02/01/2006 15:04:05": cannot parse "" as "02"
1936 - 2019-02-05 15:02:31 +0000 UTC
1937 - 2019-02-05 16:02:40 +0000 UTC
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x8 pc=0x10e58cc]
goroutine 1 [running]:
main.main()
/Users/gtheys/Code/go/src/github.axa.com/KTAXA/claims-headers-poc/issue.go:49 +0x1bc
exit status 2
I have tried to execute time.Parse using an empty string, it Didn't work. So the problem should be here. But I have no idea how to handle it graceful in following code:
package main
import (
"encoding/csv"
"fmt"
"io"
"os"
"time"
"github.com/gocarina/gocsv"
)
type DateTime struct {
time.Time
}
type Issue struct {
NotifyNo string
NotifyDate DateTime
}
// Convert the CSV string as internal date
func (date *DateTime) UnmarshalCSV(csv string) (err error) {
fmt.Println("record: ", csv)
date.Time, err = time.Parse("02/01/2006 15:04:05", csv)
return err
}
func main() {
//const layout = "02/05/2006 15:04:00"
issuesFile, err := os.OpenFile("ISSUES.txt", os.O_RDWR|os.O_CREATE, os.ModePerm)
if err != nil {
panic(err)
}
defer issuesFile.Close()
issues := []*Issue{}
gocsv.SetCSVReader(func(in io.Reader) gocsv.CSVReader {
r := csv.NewReader(in)
r.Comma = '|'
return r // Allows use pipe as delimiter
})
if err := gocsv.UnmarshalFile(issuesFile, &issues); err != nil {
fmt.Println(err)
}
for _, issue := range issues {
fmt.Println(issue.NotifyNo, " - ", issue.NotifyDate)
}
if _, err := issuesFile.Seek(0, 0); err != nil { // Go to the start of the file
panic(err)
}
}
the csv input file:
NotifyNo|NotifyDate|
1936|05/02/2019 15:02:31|
1937|05/02/2019 16:02:40|
1938||
1951|05/02/2019 17:02:19|

How can I make use of offset value in enconding/json's UnmarshalTypeError for better error handling?

A little over a year ago, Go added an Offset value to the json.UnmarshalTypeError type (see closed issue here for context). The purpose behind the offset value makes sense, but I'm not sure how it can be used when reading a go http response body, which is of type io.ReadCloser.
// An UnmarshalTypeError describes a JSON value that was
// not appropriate for a value of a specific Go type.
type UnmarshalTypeError struct {
Value string // description of JSON value - "bool", "array", "number -5"
Type reflect.Type // type of Go value it could not be assigned to
Offset int64 // error occurred after reading Offset bytes
}
For example:
var body CustomType
decoderErr := json.NewDecoder(response.Body).Decode(&body)
if decoderErr != nil {
if typeError, ok := decoderErr.(*json.UnmarshalTypeError); ok {
// Do something with typeError.Offset here
}
}
At the point of the error getting caught, I've already read from response.Body via json.NewDecoder.... I'm looking for a way to read response.Body again, but only up to the point of the error by using the Offset value in typeError.
Since you want to reuse the request body you should read and store the body before you Unmarshal the body, then if there is a JSON syntax or type error you can return a more useful error using the body you previously stored.
Proof of concept:
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
)
type Hello struct {
Name string `json:"name"`
Message string `json:"message"`
}
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
b, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "Error reading body", 400)
return
}
h := &Hello{}
if err := json.Unmarshal(b, &h); err != nil {
var msg string
switch t := err.(type) {
case *json.SyntaxError:
jsn := string(b[0:t.Offset])
jsn += "<--(Invalid Character)"
msg = fmt.Sprintf("Invalid character at offset %v\n %s", t.Offset, jsn)
case *json.UnmarshalTypeError:
jsn := string(b[0:t.Offset])
jsn += "<--(Invalid Type)"
msg = fmt.Sprintf("Invalid value at offset %v\n %s", t.Offset, jsn)
default:
msg = err.Error()
}
http.Error(w, msg, 400)
return
}
w.Write([]byte(`Good to go!`))
})
if err := http.ListenAndServe(":8000", nil); err != nil {
log.Fatal(err)
}
}

Golang UnmarshalTypeError missing Offset

I'm totally newbie in Golang and solving problem with parsing JSON. Everything is working, except error handling.
if err := json.Unmarshal(file, &configData); err != nil {
if ute, ok := err.(*json.UnmarshalTypeError); ok {
fmt.Printf("UnmarshalTypeError %v - %v - %v", ute.Value, ute.Type, ute.Offset)
}
}
Here I get error ute.Offset undefined (type *json.UnmarshalTypeError has no field or method Offset) but in Docs of JSON package and also code they have this variable in UnmarshalTypeError struct.
What I'm doing wrong? Thank you
According to the godoc description:
If a JSON value is not appropriate for a given target type, or if a JSON number overflows the target type, Unmarshal skips that field and completes the unmarshalling as best it can. If no more serious errors are encountered, Unmarshal returns an UnmarshalTypeError describing the earliest such error.
Just like string type unmarshals into chan type,it make a UnmarshalTypeError error,just like the following:
package main
import (
"encoding/json"
"fmt"
)
type A struct {
Name string
Chan chan int
}
func main() {
var a A
bs := []byte(`{"Name":"hello","Chan":"chan"}`)
if e := json.Unmarshal(bs, &a); e != nil {
if ute, ok := e.(*json.UnmarshalTypeError); ok {
fmt.Printf("UnmarshalTypeError %v - %v - %v\n", ute.Value, ute.Type, ute.Offset)
} else {
fmt.Println("Other error:", e)
}
}
}
Outputs:
UnmarshalTypeError string - chan int - 29
It is working properly!