[Igor Sobreira] Decoding JSON numbers into strings in Go

You have a JSON documents with numbers:

{
    "num_integer": 10,
    "num_float": 10.5
}

Go’s JSON decoder is smart enough to decode those into your struct with correct types, like the one bellow:

type Message struct {
    NumInt   int     json:"num_integer"
    NumFloat float64 json:"num_float"
}

If the JSON value doesn’t match your struct type encoding/json will return an error. Trying to decode the JSON document above with the following struct:

type Message struct {
    NumInt   string  json:"num_integer"
    NumFloat float64 json:"num_float"
}

will return an error:

json: cannot unmarshal number into Go value of type string

Yesterday I had this specific scenario where I needed just this: having JSON document with numbers and decode them into my struct with string fields. I found the solution in stackoverflow (of course). encoding/json has a Number type which stores the value as a string and has methods to convert to integer or floats. Here is how I used it:

type Message struct {
    NumInt   json.Number json:"num_integer"
    NumFloat json.Number json:"num_float"
}

just decode it as usual. Note how the underlying type is still a string, but you also have convenient methods to convert to both int64 or float64

var message = {
    "num_integer": 10,
    "num_float": 10.5
}
var msg Message
if err := json.Unmarshal([]byte(message), &msg); err != nil {
    panic(err)
}
fmt.Printf("%#v\n", msg)

numInt, err := msg.NumInt.Int64()
if err != nil {
    panic(err)
}
numFloat, err := msg.NumFloat.Float64()
if err != nil {
    panic(err)
}
fmt.Printf("Integer: %v. Float: %v\n", numInt, numFloat)

Here is the full example at play.golang.org

I still had to change my struct field from string to json.Number, but that’s ok because I can still assign string literals to it. By the way that’s an important distinction. string and json.Number are different types with the same underlying type. From the spec:

Each type T has an underlying type: If T is one of the predeclared boolean, numeric, or string types, or a type literal, the corresponding underlying type is T itself. Otherwise, T’s underlying type is the underlying type of the type to which T refers in its type declaration.

This works:

type Message struct {
    Num json.Number
}
var msg Message
msg.Num = "ten"    // string literal

but this doesn’t:

type Message struct {
    Num json.Number
}
var msg Message
var num = "ten"    // variable of type string
msg.Num = num

cannot use num (type string) as type json.Number in assignment

oh, and just for completeness, this also works:

type Message struct {
    Num json.Number
}
var msg Message
const num = "ten"  // now an untyped constant
msg.Num = num