You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Golang自定义JSON反序列化:如何保留整数精度存入map[string]any

解决JSON反序列化时整数转int64避免精度丢失的思路

当处理未知结构的JSON时,要把数值尽可能转成int64而非默认的float64,核心思路是绕过默认的float64自动转换,手动控制数值类型解析,具体可通过标准库json.Decoder配合递归处理实现:


核心方案:json.Decoder.UseNumber() + 递归类型转换

默认json.Unmarshal会把所有数值解析为float64,而json.Decoder的UseNumber()方法会将数值保留为json.Number(本质是字符串),这样我们可以手动判断数值类型,再转换为对应的int64/float64。

实现代码

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"math/big"
)

func decodeJSON(data []byte) (any, error) {
	dec := json.NewDecoder(bytes.NewReader(data))
	// 启用UseNumber,将数值解析为json.Number而非float64
	dec.UseNumber()

	var val any
	if err := dec.Decode(&val); err != nil {
		return nil, err
	}

	return convertNumbers(val)
}

// 递归遍历JSON结构,将json.Number转换为int64/float64(或big.Int处理超大整数)
func convertNumbers(val any) (any, error) {
	switch v := val.(type) {
	case json.Number:
		// 优先尝试转换为int64,成功则返回
		if intVal, err := v.Int64(); err == nil {
			return intVal, nil
		}
		// 若int64转换失败,尝试用big.Int存储超大整数(可选)
		var bigInt big.Int
		if _, ok := bigInt.SetString(string(v), 10); ok {
			return &bigInt, nil
		}
		// 最后转换为float64
		floatVal, err := v.Float64()
		if err != nil {
			return nil, err
		}
		return floatVal, nil
	case map[string]any:
		// 递归处理嵌套对象
		for key, value := range v {
			converted, err := convertNumbers(value)
			if err != nil {
				return nil, err
			}
			v[key] = converted
		}
		return v, nil
	case []any:
		// 递归处理数组
		for i, item := range v {
			converted, err := convertNumbers(item)
			if err != nil {
				return nil, err
			}
			v[i] = converted
		}
		return v, nil
	default:
		// 其他类型(string、bool、null)直接返回
		return v, nil
	}
}

func main() {
	jsonData := []byte(`{
		"id": 1234567890123456789,
		"price": 9.99,
		"count": 42,
		"hugeInt": 9223372036854775808,
		"nested": {
			"bigInt": 9876543210987654321
		},
		"array": [1, 2.5, 3]
	}`)

	result, err := decodeJSON(jsonData)
	if err != nil {
		log.Fatal(err)
	}

	m := result.(map[string]any)
	fmt.Printf("id type: %T, value: %v\n", m["id"], m["id"])           // int64, 1234567890123456789
	fmt.Printf("price type: %T, value: %v\n", m["price"], m["price"])   // float64, 9.99
	fmt.Printf("count type: %T, value: %v\n", m["count"], m["count"])   // int64, 42
	fmt.Printf("hugeInt type: %T, value: %v\n", m["hugeInt"], m["hugeInt"]) // *big.Int, 9223372036854775808

	nested := m["nested"].(map[string]any)
	fmt.Printf("bigInt type: %T, value: %v\n", nested["bigInt"], nested["bigInt"]) // int64, 9876543210987654321

	array := m["array"].([]any)
	fmt.Printf("array[0] type: %T, value: %v\n", array[0], array[0]) // int64, 1
	fmt.Printf("array[1] type: %T, value: %v\n", array[1], array[1]) // float64, 2.5
}

方案说明

  1. UseNumber()的作用:阻止解码器自动将数值转为float64,保留原始字符串形式的数值,从根源避免精度丢失。
  2. 递归处理逻辑:遍历JSON所有嵌套层级(对象、数组),对每个json.Number做分层判断:
    • 能转成int64的整数直接存为int64;
    • 超出int64范围的超大整数用big.Int存储(可选,彻底避免精度丢失);
    • 浮点数或无法转整数的数值转为float64。
  3. 兼容性:基于标准库实现,无需第三方依赖,支持所有JSON基本类型和嵌套结构。

注意点

  • 若业务无需处理超大整数,可以去掉big.Int转换逻辑,简化代码;
  • 使用时需要对map中的值做类型断言(比如判断是int64还是float64),因为map的value是any类型。

内容的提问来源于stack exchange,提问作者Sigma

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 00:22:10