Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get JSON when decoder fails to unmarshal?

Tags:

json

go

I am using a json.Decoder to decode JSON delivered over a network stream. It works fine, but whenever someone sends data that doesn't fit the schema (e.g. sending a negative integer when the struct's field type is unsigned) it returns an error with a vague message that doesn't pinpoint the offending property. This makes debugging more difficult.

Is there any way to extract the JSON that the decoder was trying to unmarshal when it errored? Here's a small reproducable snippet:

package main

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

func main() {
    buff := bytes.NewBufferString("{\"bar\": -123}")
    decoder := json.NewDecoder(buff)

    var foo struct{
        Bar uint32
    }
    if err := decoder.Decode(&foo); err != nil {
        fmt.Println(err)
        fmt.Println("TODO: how to get JSON that caused this error?")
    } else {
        fmt.Println(foo.Bar)
    }
}

Or on playground: https://play.golang.org/p/-WdYBkYEzJ

like image 696
Phil K Avatar asked Sep 05 '25 03:09

Phil K


2 Answers

Some information is in the error, which is of type *json.UnamrshalTypeError

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
}

You can get the offset in the json string, the reflect.Type of the field being unmarshaled into, and the json description of the Value. This can still pose a problem for types that implement their own unmarshaling logic, which is referenced by issue 11858

like image 122
JimB Avatar answered Sep 07 '25 19:09

JimB


As of Go 1.8 this is now possible. The UnmarshalTypeError type now contains Struct and Field values which provide the name of the struct and field which caused a type mismatch.

package main

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

func main() {
    buff := bytes.NewBufferString("{\"bar\": -123}")
    decoder := json.NewDecoder(buff)

    var foo struct{
        Bar uint32
    }
    if err := decoder.Decode(&foo); err != nil {
        if terr, ok := err.(*json.UnmarshalTypeError); ok {
                fmt.Printf("Failed to unmarshal field %s \n", terr.Field)
        } else {
            fmt.Println(err)
        }
    } else {
        fmt.Println(foo.Bar)
    }
}

The error message string also was changed to contain this new information.

Go 1.8:

json: cannot unmarshal number -123 into Go struct field .Bar of type uint32

Go 1.7 and earlier:

json: cannot unmarshal number -123 into Go value of type uint32
like image 38
Phil K Avatar answered Sep 07 '25 20:09

Phil K