Search code examples
jsongomarshallingunmarshalling

Inconsistency in numbers between json Marshall and Unmarshall in Golang


package main

import (
    "encoding/json"
    "fmt"
)

func t() {
    a := int64(1<<63 - 1)
    fmt.Println(a)
    m := map[string]interface{}{
        "balance": a,
    }

    data, _ := json.Marshal(m)
    m1 := make(map[string]interface{})
    err := json.Unmarshal(data, &m1)
    panicIF(err)
    fmt.Println(int64(m1["balance"].(float64)))
}

func panicIF(err error) {
    if err != nil {
        panic(err)
    }
}

Running above code will print:

9223372036854775807
-9223372036854775808

Can you please explain the issue? I read somewhere that in JSON we should not use numbers > 2^53 with number type, rather it should be string type.


Solution

  • When unmarshaling into a map of type map[string]any, the encoding/json package will choose float64 type to unmarshal numbers.

    The number 9223372036854775807 cannot be represented precisely by a value of type float64, so some other number close to it will be represented, here namely 9223372036854775808.0. When you convert this number to int64, since it is outside of the valid range of int64, the result will obviously be something else, in your case the minimum of the representable int64 values.

    Add some extra logging statements:

    data, err := json.Marshal(m)
    panicIF(err)
    fmt.Println(string(data))
    m1 := make(map[string]interface{})
    err = json.Unmarshal(data, &m1)
    panicIF(err)
    fmt.Println(m1)
    fmt.Printf("%f\n", m1["balance"].(float64))
    fmt.Println(int64(m1["balance"].(float64)))
    

    This will output (try it on the Go Playground):

    9223372036854775807
    {"balance":9223372036854775807}
    map[balance:9.223372036854776e+18]
    9223372036854775808.000000
    -9223372036854775808