Golang:JSON和int64、interface的那些事儿

本文介绍了Golang中JSON反序列化时可能遇到的精度丢失问题,特别是针对int64类型。由于JSON中数字默认解析为float64,可能导致精度丢失。解决方法是将数据解析到interface{},并使用json.Number存储原始字符串。在比较两个由JSON反序列化的map时,需要注意处理number类型的值,避免直接用int64进行比较,而应转换为字符串进行判断。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

先来看一下 : JSON number的那些事


golang 数据类型和json类型对照关系表:

bool, for JSON booleans
float64, for JSON numbers
string, for JSON strings
[]interface{}, for JSON arrays
map[string]interface{}, for JSON objects
nil for JSON null

json中没有int64类型的,反序列化的时候,如果直接用json.Unmarshal 可能会精度丢失
可以参考这篇文章:json 反序列化数字精度丢失

原理
解析 JSON 的时候其实是对 JSON 串的遍历,其实所有遍历出来的值都是[]byte类型,然后根据识别目标解析对象字段类型,或者识别[]byte数据的内容转换格式。

比如,如果数据被解析到int上,把[]byte转换为int;
如果被解析到interface{}上,就只能通过[]byte的类型来转换了,
而且数字会被统一处理成float64,这个有个问题,就是会丢精度。
而通过Number解析时,值会被直接保存为字符串类型

正确做法:

func DecodeMyStruct(jsonStr string) (*MyStruct, error) {
	var regular *MyStruct
	decoder := jsoniter.NewDecoder(strings.NewReader(jsonStr))
	//使用UseNumber()可以使得json中的大整数转换时,不会转换成浮点数float64,但是要用decoder不能用json.Marshal
	decoder.UseNumber() 
	err := decoder.Decode(&regular)
	if err != nil {
		return nil, err
	}
	return &regular, nil
}

其中 MyStruct是我们需要反序列化的类型,可以是struct,slice, map等等.

如果不知道应该反序列化成啥类型,可以是map[string]interface{},用的时候可以配合断言使用,

比如下面一种场景:需要对比两个json反序列化成map[string]interface{}的结构,
判断:filterAttrMap 是不是包含 conditionMap的所有键值对。

func CompareRecursiveV2(ctx context.Context, filterAttrMap, conditionMap map[string]interface{}) bool {
	for conditionKey, conditionValue := range conditionMap {
		filterValue, filterOk := filterAttrMap[conditionKey]
		if !filterOk {
			return false
		}
		// 断言判断值interface{}的类型
		switch conditionValue.(type) {
		case json.Number:      // !!!!这里不能是int64
			filterJson, ok1 := filterValue.(json.Number)
			conditionJson, ok2 := conditionValue.(json.Number)
			filter := filterJson.String()
			condition := conditionJson.String()
			if !ok1 || !ok2 || filter != condition {
				return false
			}
		case string:
			filter, ok1 := filterValue.(string)
			condition, ok2 := conditionValue.(string)
			if !ok1 || !ok2 || filter != condition {
				return false
			}
		case bool:
			filter, ok1 := filterValue.(bool)
			condition, ok2 := conditionValue.(bool)
			if !ok1 || !ok2 || filter != condition {
				return false
			}
		default:
			conditionValueMap, ok1 := conditionValue.(map[string]interface{})
			if !ok1 {
				logs.CtxFatal(ctx, "conf error, only support[int64, string, bool, map]")
				return false
			}
			filterValueMap, ok2 := filterValue.(map[string]interface{})
			if !ok1 || !ok2 {
				return false
			}
			// value类型还是map[string]interface{},递归解析比较
			return CompareRecursiveV2(ctx, filterValueMap, conditionValueMap)
		}
	}
	return true
}

上面case的第一项不能是 int64, 因为两个比较的map是有json转来的,interface{} 里面数字只有number类型(实际上是string存储的),所以,要case number类型,并且转化成字符串判断两个value是不是一样。

这里和下面情况不一样,下面例子:updateStudent 也是 map[string]interface{}类型
但是updateStudent不是json转换的,是代码显式赋值的

    var student_id int64 = 123456
    updateStudent["student_id"]= student_id
    id, ok := updateStudent["student_id"].(int64) // 可以用int64
	if !ok {
		fmt.print("断言失败, key:stedent_id 不是int64类型")
	} else {
		fmt.printf(" key:stedent_id 是int64类型, 并且值为:%d", id)
	}

补充另外一种两个map[string]interface{}的比较,这里多了一个regular,也是map[string]interface{}类型的
是一个云上配置的json结构,我们可以动态的在云上配置这个regular,来定义两个map需要比较的字段和字段的类型(key和value都设置成了string类型,方便switch-case)
在每次需要对比的时候,就通过客户端从云上拉取这个json,转换成map[string]interface{},进行比较
比如:

{
    "student_id":"int64",
    "student_name":"string",
    "genre":"int64",
    "status":"int64",
    "extra_struct":{
        "father":"string",
        "risk_rate":"int64",
        "dynamic_struct":{
            "yuwen_score":"int64",
            "shuxue_score":"int64"
        }
    }
}
func CompareRecursive(ctx context.Context, regular, filterAttrMap, conditionMap map[string]interface{}) bool {
	for regularKey, regularValue := range regular {
		filterValue, filterOk := filterAttrMap[regularKey]
		conditionValue, conditionOk := conditionMap[regularKey]
		if filterOk && conditionOk {
			if regularValue == "int64" {
				filterJson, ok1 := filterValue.(json.Number)
				conditionJson, ok2 := conditionValue.(json.Number)
				filter := filterJson.String()
				condition := conditionJson.String()
				if !ok1 || !ok2 || filter != condition {
					return false
				}
			} else if regularValue == "string" {
				filter, ok1 := filterValue.(string)
				condition, ok2 := conditionValue.(string)
				if !ok1 || !ok2 || filter != condition {
					return false
				}
			} else if regularValue == "bool" {
				filter, ok1 := filterValue.(bool)
				condition, ok2 := conditionValue.(bool)
				if !ok1 || !ok2 || filter != condition {
					return false
				}
			} else {
				regularValueMap, ok := regularValue.(map[string]interface{})
				if !ok {
					logs.CtxFatal(ctx, "%s conf error, support[int64, string, bool, map]", regularKey)
					return false
				}
				filterValueMap, ok1 := filterValue.(map[string]interface{})
				conditionValueMap, ok2 := conditionValue.(map[string]interface{})
				if !ok1 || !ok2 {
					return false
				}
				return CompareRecursive(ctx, regularValueMap, filterValueMap, conditionValueMap)
			}
		} else if !filterOk && conditionOk {
			return false
		}
	}
	return true
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值