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
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
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
You can get at each element, key, value, and even delimiters using decode.Token() such as this in the playground, and below (modified from your example):
package main
import (
"bytes"
"encoding/json"
"fmt"
)
func main() {
buff := bytes.NewBufferString(`{"foo": 123, "bar": -123, "baz": "123"}`)
decoder := json.NewDecoder(buff)
for {
t, err := decoder.Token()
if _, ok := t.(json.Delim); ok {
continue
}
fmt.Printf("type:%11T | value:%5v //", t, t)
switch t.(type) {
case uint32:
fmt.Println("you don't see any uints")
case int:
fmt.Println("you don't see any ints")
case string:
fmt.Println("handle strings as you will")
case float64:
fmt.Println("handle numbers as you will")
}
if !decoder.More() {
break
}
if err != nil {
fmt.Println(err)
}
}
}
This will output
type: string | value: foo //handle strings as you will
type: float64 | value: 123 //handle numbers as you will
type: string | value: bar //handle strings as you will
type: float64 | value: -123 //handle numbers as you will
type: string | value: baz //handle strings as you will
type: string | value: 123 //handle strings as you will
You can switch on the type and handle each one as you wish. I have shown a simple example of that as well, each of the "//comments" in the result are conditional based on the type.
You'll also notice that each numbers' type is float64, although they would fit into an int, or even uint in the case of the "foo" value, and I check for those types in the switch but they never get used. You would have to provide your own logic in order to convert the float64 values to the types you wanted if they could be and handle types that wouldn't convert as special cases, or errors or whatever you wanted.
Related
What I want to do is commented:
type foo struct {
Message string `json:"message"`
}
func bar() {
//The "message" field contains a bool type which does not
//match the equivalent "message" field in foo, a string type
jsonData := byte[]("{
\"message\": true
}")
var baz foo
//Because the type of "message" in the json bytes is
//bool and not a string, put "" inside baz.Message
json.Unmarshal(jsonData, &baz)
}
How do I Unmarshal the json byte array, then only populate the particular field if the field matches the type in the json byte array field? And if the field does type does not match the json byte array field type, put in a placeholder value of nil, "" etc. ?
There are many ways to handle this, once you fix your question to something that can be answered. The one I like is to unmarshal to a variable of type interface{}, which you can then inspect:
package main
import (
"encoding/json"
"fmt"
)
type foo struct {
Message interface{} `json:"message"`
}
func bar() {
//The "message" field contains a bool type which does not
//match the equivalent "message" field in foo, a string type
jsonData := []byte(`{
"message": true
}`)
var baz foo
//Because the type of "message" in the json bytes is
//bool and not a string, put "" inside baz.Message
err := json.Unmarshal(jsonData, &baz)
fmt.Printf("unmarhal error is: %v\n", err)
if err == nil {
fmt.Printf("baz.Message is now: %T = %v\n", baz.Message, baz.Message)
}
}
func main() {
bar()
}
(Go Playground link)
It should now be fairly obvious how to switch on the type (after decoding) and see if what you got is what you want. If so, use it. If not, use your default. If necessary, decode the incoming json to more-generic Go types first, then fill in the specific type you really want to handle.
The Unmarshaler interface in the encoding/json package allows you to have full control over how the data is decoded into your types.
You only need to implement one method on your type - UnmarshalJSON(data []byte).
In the example below, I've declared a temporary anonymous type in my UnmarshalJSON method and decoded the json into an empty interface.
I can then type assert the interface into a string, and on success I set the Message field on my original Foo type. We don't need to handle the failure case, since strings in Go have a default value of "".
Here's a working example.
type Foo struct {
Message string
}
func (this *Foo) UnmarshalJSON(data []byte) error {
raw := struct {
Message interface{} `json:"message"`
}{}
err := json.Unmarshal(data, &raw)
if err != nil {
return err
}
if msgString, ok := raw.Message.(string); ok {
this.Message = msgString
}
return nil
}
I have the following data structures which I'd like to parse from an API:
type OrderBook struct {
Pair string `json:"pair"`
UpdateTime int64 `json:"update_time"`
}
type depthResponse struct {
Result OrderBook `json:"result"`
// doesn't matter here
//Cmd string `json:"-"`
}
and when I parse the following:
data := `{"error":{"code":"3016","msg":"交易对错误"},"cmd":"depth"}`
It doesn't fail. Why?
Full source code (playground)
package main
import (
"encoding/json"
"fmt"
"log"
"strings"
)
type OrderBook struct {
Pair string `json:"pair"`
UpdateTime int64 `json:"update_time"`
}
type depthResponse struct {
Result OrderBook `json:"result"`
}
func main() {
data := `{"error":{"code":"3016","msg":"交易对错误"},"cmd":"depth"}`
r := strings.NewReader(data)
var resp depthResponse
if err := json.NewDecoder(r).Decode(&resp); err != nil {
log.Fatalf("We should end up here: %v", err)
}
fmt.Printf("%+v\n", resp)
}
That's the expected behaviour of Decode (as documented in the Unmarshal function):
https://golang.org/pkg/encoding/json/#Unmarshal
By default, object keys which don't have a corresponding struct field are ignored.
You can however use the DisallowUnknownFields() function (as described in the docs as well) to have it fail if the input JSON has fields not contained in the destination struct.
dec := json.NewDecoder(r)
dec.DisallowUnknownFields()
In that case, you'll get an error as you expect.
Modified playground here: https://play.golang.org/p/A0f6dxTXV34
I'm trying to create a JSON representation within Go using a map[string]interface{} type. I'm dealing with JSON strings and I'm having a hard time figuring out how to avoid the JSON unmarshaler to automatically deal with numbers as float64s. As a result the following error occurs.
Ex.
"{ 'a' : 9223372036854775807}" should be map[string]interface{} = [a 9223372036854775807 but in reality it is map[string]interface{} = [a 9.2233720368547758088E18]
I searched how structs can be used to avoid this by using json.Number but I'd really prefer using the map type designated above.
The go json.Unmarshal(...) function automatically uses float64 for JSON numbers. If you want to unmarshal numbers into a different type then you'll have to use a custom type with a custom unmarshaler. There is no way to force the unmarshaler to deserialize custom values into a generic map.
For example, here's how you could parse values of the "a" property as a big.Int.
package main
import (
"encoding/json"
"fmt"
"math/big"
)
type MyDoc struct {
A BigA `json:"a"`
}
type BigA struct{ *big.Int }
func (a BigA) UnmarshalJSON(bs []byte) error {
_, ok := a.SetString(string(bs), 10)
if !ok {
return fmt.Errorf("invalid integer %s", bs)
}
return nil
}
func main() {
jsonstr := `{"a":9223372036854775807}`
mydoc := MyDoc{A: BigA{new(big.Int)}}
err := json.Unmarshal([]byte(jsonstr), &mydoc)
if err != nil {
panic(err)
}
fmt.Printf("OK: mydoc=%#v\n", mydoc)
// OK: mydoc=main.MyDoc{A:9223372036854775807}
}
func jsonToMap(jsonStr string) map[string]interface{} {
result := make(map[string]interface{})
json.Unmarshal([]byte(jsonStr), &result)
return result
}
Example - https://goplay.space/#ra7Gv8A5Heh
Related questions - create a JSON data as map[string]interface with the given data
My struct:
type User struct {
FirstName string `json:"firstname, omitempty" validate:"required"`
LastName string `json:"lastname, omitempty" validate:"required"`
NumberofDays int `json:"numberofdays, string" validate:"min=0,max=100"`
}
Value for NumberofDays is passed as string from the server but I want to check if it is within range and store as int.
Ex: user := &User{"Michael","Msk","3"}
I'm getting 'cannot unmarshal string into Go value of type int'.
I'm not sure how to typecast to int and do the validation
Remove the space after the comma in the struct tags, e.g. json:"numberofdays, string" should be json:"numberofdays,string". With the space, the json package ignores the string part, hence the error message you're getting. Without the space, the error goes away and behavior is as expected.
Demo comparing the two here: https://play.golang.org/p/AUImnw_PIS
Documentation of json package struct tags: https://golang.org/pkg/encoding/json/#Marshal
You can use a custom type to unmarshal a string
to an integer value using whatever set of parsing rules
you want this mapping to use:
package main
import (
"encoding/json"
"fmt"
"strconv"
)
type User struct {
FirstName string `json:"firstname,omitempty" validate:"required"`
LastName string `json:"lastname,omitempty" validate:"required"`
NumberofDays StrInt `json:"numberofdays" validate:"min=0,max=100"`
}
type StrInt int
func (si *StrInt) UnmarshalJSON(b []byte) error {
var s string
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
n, err := strconv.ParseInt(s, 10, 0)
if err != nil {
return err
}
*si = StrInt(n)
return nil
}
const data = `{
"FirstName": "John",
"LastName": "Doe",
"NumberOfDays": "42"
}`
func main() {
var u User
err := json.Unmarshal([]byte(data), &u)
if err != nil {
panic(err)
}
fmt.Println(u)
}
Playground.
The idea is as follows:
Have a custom type which is mostly int but allows defining custom methods on it.
Have that type satisfy the encoding/json.Unmarshaler interface by implementing the UnmarshalJSON() method on a pointer to that type.
In the method, first unmarshal the value as its native type—string—and
then parse it as an integer.
This approach might appear to be weird, but if you'll think of it
a bit more, there's no single "true" string representation of an integer:
there are different bases and different conventions at representing
integers even in a particular base.
In your unmarshaling method you implement the policy of the text representation
of your integers.
The json package defines a custom type json.Number for these cases.
In this stackoverflow post it's explained how to add arbitrary fields to a golang struct by using it as an anonymous. This works fine if you are working with known struct types, but I'm wondering how to do the same thing when dealing with an unknown struct or interface.
I wrote the following example to demonstrate:
package main
import (
"os"
"encoding/json"
"fmt"
)
type example interface{}
type Data struct {
Name string
}
func printInterface(val interface{}) {
example1 := struct {
example
Extra string
}{
example: val,
Extra: "text",
}
json.NewEncoder(os.Stdout).Encode(example1)
}
func printStructPointer(val *Data) {
example2 := struct {
*Data
Extra string
}{
Data: val,
Extra: "text",
}
json.NewEncoder(os.Stdout).Encode(example2)
}
func main() {
d := Data{Name:"name"}
fmt.Println("Example 1:")
printInterface(&d)
fmt.Println("Example 2:")
printStructPointer(&d)
}
This prints the following:
Example 1:
{"example":{"Name":"name"},"Extra":"text"}
Example 2:
{"Name":"name","Extra":"text"}
I'm so assuming that I was working within printInterface how do get the JSON output to look like the JSON output of printStructPointer?
There's an important difference between printInterface() and printStructPointer(). The first one embeds an interface type, while the second embeds a struct type (more specifically a pointer to a struct type).
When you embed a struct (or pointer to struct) type, the fields of the embedded type get promoted, so in the 2nd example it will be valid to write example2.Name. When you embed an interface type, an interface does not have fields, so no fields will be promoted. So it doesn't matter if the interface value wraps a struct (or pointer to struct), fields of that struct won't get promoted (they can't be).
Thus, in the printInterface() the interface wrapping a struct won't get "flattened" in the JSON result.
Solving it with generating a dynamic type using reflection
One way to solve this is to generate a dynamic type at runtime, using reflection (reflect package). This new type will be a struct, and it will contain an anonymous struct field being of the type that is wrapped in the passed interface, and will also contain our extra field (of type string).
This is how it could look like:
func printInterface(val interface{}) {
t2 := reflect.StructOf([]reflect.StructField{
reflect.StructField{
Name: "X",
Anonymous: true,
Type: reflect.TypeOf(val),
},
reflect.StructField{
Name: "Extra",
Type: reflect.TypeOf(""),
},
})
v2 := reflect.New(t2).Elem()
v2.Field(0).Set(reflect.ValueOf(val))
v2.FieldByName("Extra").SetString("text")
json.NewEncoder(os.Stdout).Encode(v2.Interface())
}
Output is as expected (try it on the Go Playground):
Example 1:
{"Name":"name","Extra":"text"}
Example 2:
{"Name":"name","Extra":"text"}
Solving it with marshaling twice
Another way would be to marshal the value, unmarshal it into a map, add the extra field and marshal it again:
func printInterface(val interface{}) error {
data, err := json.Marshal(val)
if err != nil {
return err
}
v2 := map[string]interface{}{}
if err := json.Unmarshal(data, &v2); err != nil {
return err
}
v2["Extra"] = "text"
return json.NewEncoder(os.Stdout).Encode(v2)
}
Output is the same. Try it on the Go Playground.
This solution is simpler, easier to follow, but it's slower as it marshals twice. Also note that in this example the fields in the result might be in different order, as iteration order on a map is not specified in Go (for details see Why can't Go iterate maps in insertion order?).
Here's one way:
package main
import (
"encoding/json"
"fmt"
"os"
)
type example interface{}
type Data struct {
Name string
}
func printInterface(val interface{}) {
if d, ok := val.(*Data); ok {
fmt.Println("Asserting type *Data for val is OK")
printStructPointer(d)
} else {
fmt.Println("Asserting type *Data for val is NOT OK")
}
}
func printStructPointer(val *Data) {
example2 := struct {
*Data
Extra string
}{
Data: val,
Extra: "text",
}
json.NewEncoder(os.Stdout).Encode(example2)
}
func main() {
d := Data{Name: "Testing"}
fmt.Println("Example 1:")
printInterface(&d)
fmt.Println("Example 2:")
printStructPointer(&d)
}
Playground: https://play.golang.org/p/OPotpTyUEz
You can also possibly use a type switch for an assertion, esp if you have many types. Hope this helps!