登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go问答

Go JSON 数字解码到 int 为什么会溢出

来源:17golang原创

时间:2026-10-14 22:41:49 451浏览 收藏

我在接一个外部 JSON 接口时遇到过一个很容易误判的问题:请求整体看起来是合法 JSON,字段名也能对应上,但把一个数字解码到 Go 的整数类型时,返回了 json: cannot unmarshal number ... into Go struct field ...。这不是 JSON 语法坏了,而是数字已经越过了目标 Go 类型的表示范围。

先记住结论:JSON 数字解码到 int 发生溢出时,encoding/json 会返回 *json.UnmarshalTypeError,不会替你截断、取模或自动改用更宽的整数。字段可能保持零值,其他字段也只是“尽力”继续解码,不能把返回错误当成提示信息而忽略。

JSON 数字超过 Go 整数目标类型范围并形成 UnmarshalTypeError 的静态结构图
图1:JSON 数字、Go 整数范围与 UnmarshalTypeError 的边界关系说明图,不是运行截图。

先确认溢出发生在哪个类型边界

JSON 规范允许数字以数字字面量出现,但它没有替 Go 代码决定最终要放进 int、int64 还是字符串。真正写入结构体字段时,encoding/json 必须检查目标类型能否容纳这个值。

这里还有一个容易踩的点:int 的宽度跟平台有关。64 位 Go 环境通常可以表示 64 位有符号整数,32 位环境的 int 范围则更小。因此,用 2147483648 演示时,在 int32 上一定越界,但在 64 位 int 上未必越界;要测试 64 位 int,可以使用超过 int64 上限的数字。

package main

import (
	"encoding/json"
	"fmt"
)

type Payload struct {
	Count int32  `json:"count"` // 用固定宽度类型,让示例边界不受平台影响
	Name  string `json:"name"`  // 观察数字出错时其它字段的解码结果
}

func main() {
	data := []byte(`{"count":2147483648,"name":"demo"}`)
	var p Payload
	if err := json.Unmarshal(data, &p); err != nil {
		// 数值超出 int32 范围时,标准库返回类型错误而不是自动截断
		fmt.Printf("decode failed: %v\n", err)
	}
	fmt.Printf("payload: %+v\n", p)
}

这个例子里,问题在“目标类型范围”而不是字段名或 JSON 逗号。生产代码应把错误当成一次解码失败处理;不要因为 Name 仍然被读出来,就认为整个对象已经可靠。

从 UnmarshalTypeError 取出具体字段

只打印 err.Error() 能看到一行描述,但接口层通常还需要知道是哪个 JSON 值、哪个 Go 类型和哪个字段出错。官方类型 UnmarshalTypeError 提供了 Value、Type、Offset、Struct 和 Field 等定位信息,可以通过 errors.As 提取。

package main

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

type Payload struct {
	Count int32 `json:"count"` // 目标类型故意设为固定宽度,便于定位范围错误
}

func decode(data []byte) error {
	var p Payload
	if err := json.Unmarshal(data, &p); err != nil {
		var typeErr *json.UnmarshalTypeError
		if errors.As(err, &typeErr) {
			// 把字段路径和目标类型写入业务日志,方便调用方修正协议
			return fmt.Errorf("field %s received %s for %s: %w", typeErr.Field, typeErr.Value, typeErr.Type, err)
		}
		// 语法错误等其它解码失败继续原样返回
		return err
	}
	return nil
}

UnmarshalTypeError 只说明“某个 JSON 值不适合某个 Go 类型”,它不是一个自动修复器。对外接口可以把它转换成明确的参数错误;对内部消费则应保留原始错误和字段路径,避免把协议问题伪装成默认值。

字段型数据怎样避免直接塞进 int

如果字段的协议含义是数量、金额分、数据库主键或外部系统编号,先问清楚它的最大范围和是否允许前导零。只要协议范围稳定,优先在结构体上表达真实边界,而不是把所有数字都写成 int。

  • 明确不超过 32 位时使用 int32,让范围约束可读、可移植。
  • 需要覆盖 64 位有符号整数时使用 int64,并在解码后继续做业务范围检查。
  • 编号可能超过 64 位,或不能丢失前导零时使用 string,把它当标识符而不是算术量。
  • 协议范围不可信时先接收为字符串或 json.Number,确认边界后再转成业务类型。
type Order struct {
	// 外部订单号只用于匹配,不参与加减,使用字符串避免精度和前导零问题
	OrderID string `json:"order_id"`
	// 金额分需要明确 64 位范围,解码成功后仍要检查业务上限
	AmountCents int64 `json:"amount_cents"`
}

如果只能修改一处,先修改协议模型和错误处理,而不是在收到错误后把字段强行改成 float64。后一种做法很可能把“解码失败”换成“业务数值悄悄变形”。

动态 JSON 怎样保留大数字

当 JSON 的字段结构运行时才知道,常见写法是解码到 map[string]any。但默认情况下,接口里的 JSON 数字会被解码成 float64;对大整数来说,这会带来整数精度问题。此时可以使用 Decoder.UseNumber,让数字先保留成 json.Number,再决定是否调用 Int64。

package main

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

func main() {
	data := `{"id":9223372036854775807}`
	dec := json.NewDecoder(strings.NewReader(data))
	dec.UseNumber() // 动态对象中的数字先保留原始文本,不直接变成 float64

	var v map[string]any
	if err := dec.Decode(&v); err != nil {
		panic(err) // 示例中输入来自受控字符串,服务代码应返回错误
	}

	n, ok := v["id"].(json.Number)
	if !ok {
		panic("id is not a JSON number") // 先确认动态字段的实际类型
	}
	id, err := n.Int64()
	if err != nil {
		panic(err) // 超出 int64 时在这里失败,不把它静默转成近似值
	}
	fmt.Println(id)
}
字段型和动态 JSON 处理大数字的两条安全分支静态说明图
图2:字段型模型与动态 JSON 分别选择宽类型、字符串或 UseNumber 的处理分支说明图,不是运行截图。

UseNumber 解决的是“先不要把动态数字变成 float64”,并不意味着任何数字都能转成 int64。转换仍然是一个明确的边界动作,失败时要把错误交给调用方或业务校验层。

哪些修复方式容易掩盖问题

第一,忽略 json.Unmarshal 的返回值。标准库可能尽力处理剩余字段,但这不等于对象满足协议;继续入库或下单会把坏数据带到更晚的阶段。

第二,把所有数字改成 float64。它适合需要小数的测量值,却不适合作为大整数编号或精确金额的默认容器。

第三,只在 64 位本机测试 int。部署到不同架构、跨语言传递或接收更大的外部编号时,平台依赖会重新暴露。对协议字段,固定宽度类型、字符串和明确的错误分支比“本机能跑”更可靠。

我现在的处理顺序是:先确认 JSON 数字的业务语义,再确认目标类型的范围;结构固定就把范围写进结构体,结构动态就用 UseNumber 延迟决定;最后用 errors.As 记录具体字段。这样 UnmarshalTypeError 是一条清晰的协议边界,而不是上线后才出现的模糊异常。

常见问题

JSON 数字大于 int 的上限,会自动变成最大值吗?

不会。对目标整数类型发生溢出时,encoding/json 返回 UnmarshalTypeError,不应依赖截断或饱和值结果。

能不能先解码到 map[string]any 再处理?

可以,但默认数字会进入 float64。需要保留大整数文本时,先对 Decoder 调用 UseNumber,再显式选择 Int64 或字符串方案。

出现 UnmarshalTypeError 后,其它字段是否可信?

不能一概而论。官方文档说明解码会尽力继续,但出错字段会被跳过,错误字段之后的字段是否已写入也没有保证。把整个解码结果视为失败更安全。

总结:Go JSON 数字解码到 int 溢出,本质是输入值与目标类型范围不匹配。固定结构优先用准确的整数宽度或字符串,动态结构先用 UseNumber 保留数字,再通过显式转换和 UnmarshalTypeError 把问题定位到字段。

官方资料:https://pkg.go.dev/encoding/json

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>