在Golang中,JSON的编组(Marshal)和解组(Unmarshal)之间的数字不一致。

huangapple go评论74阅读模式
英文:

Inconsistency in numbers between json Marshall and Unmarshall in Golang

问题

上述代码的问题在于使用了一个大于2^53的数字作为JSON对象的值。根据JSON规范,数字类型的值应该小于或等于2^53。如果超过这个范围,应该将其作为字符串类型处理。

在代码中,变量a的值为9223372036854775807,这是一个大于2^53的数字。当将其作为值赋给JSON对象的balance属性时,它被转换为浮点数类型。然后,通过JSON的编码和解码过程,将其转换回整数类型时,会出现精度丢失的问题,导致打印出的结果为-9223372036854775808。

为了解决这个问题,可以将balance的值转换为字符串类型,以确保精度不会丢失。修改代码如下:

package main

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

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

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

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

修改后的代码将balance的值转换为字符串类型,并在解码后将其转换回整数类型。这样就可以正确地打印出9223372036854775807作为结果。

英文:
package main

import (
	&quot;encoding/json&quot;
	&quot;fmt&quot;
)

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

	data, _ := json.Marshal(m)
	m1 := make(map[string]interface{})
	err := json.Unmarshal(data, &amp;m1)
	panicIF(err)
	fmt.Println(int64(m1[&quot;balance&quot;].(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.

答案1

得分: 1

当将数据反序列化为类型为map[string]any的映射时,encoding/json包将选择float64类型来反序列化数字。

数字9223372036854775807无法精确表示为float64类型的值,因此将表示为接近它的其他数字,即9223372036854775808.0。当将此数字转换为int64时,由于它超出了有效范围,结果显然会是其他值,在您的情况下是可表示的int64值的最小值。

添加一些额外的日志语句:

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)))

这将输出(在Go Playground上尝试):

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

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, &amp;m1)
panicIF(err)
fmt.Println(m1)
fmt.Printf(&quot;%f\n&quot;, m1[&quot;balance&quot;].(float64))
fmt.Println(int64(m1[&quot;balance&quot;].(float64)))

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

9223372036854775807
{&quot;balance&quot;:9223372036854775807}
map[balance:9.223372036854776e+18]
9223372036854775808.000000
-9223372036854775808

huangapple
  • 本文由 发表于 2022年6月30日 20:22:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/72815764.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定