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 代码决定最终要放进 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)
}

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
-
502 收藏
-
502 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
495 收藏
-
Golang · Go问答 | 19分钟前 | CGO · 内存管理 · Go问答 · Go CGO unsafe.Pointer 指针传递 runtime/cgo.Handle runtime.Pinner124 收藏
-
119 收藏
-
401 收藏
-
335 收藏
-
217 收藏
-
191 收藏
-
246 收藏
-
235 收藏
-
189 收藏
-
Golang · Go问答 | 38分钟前 | 标准库 · 文件读取 · Go问答 · Go archive/zip Store DEFLATE File.Open FileHeader.Method File.OpenRaw241 收藏
-
211 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习