Go json.Decoder 如何只拒绝嵌套对象中的未知字段
来源:17golang原创
时间:2026-09-11 09:26:30 110浏览 收藏
Go 的 encoding/json 默认会忽略 JSON 中没有对应 Go 字段的键。给最外层 json.Decoder 调用 DisallowUnknownFields,虽然能抓住拼写错误,却也会让上游新增一个顶层字段就直接失败。需要“只拒绝嵌套对象中的未知字段”时,做法是把严格策略放进目标嵌套类型自己的 UnmarshalJSON:外层保持普通解码,指定对象内部再创建一个开启严格模式的 Decoder。
DisallowUnknownFields没有按路径配置的参数,想局部生效要把策略放到嵌套类型。- 自定义
UnmarshalJSON时用别名类型接收数据,避免方法递归调用。 - 严格校验只对有 Go struct schema 的对象有意义,map 和 RawMessage 需要另外定义边界。
如果只想让嵌套结构体拒绝未知字段,外层字段保留宽松解析的行为,可以给对应嵌套类型单独实现
json.Unmarshaler接口,在自定义解析逻辑内部新建带DisallowUnknownFields()的临时 Decoder 处理该段嵌套 JSON 数据,外层就维持默认的宽松解析逻辑即可。
为什么全局开启严格模式不适合兼容接口
假设请求外层是 Envelope,其中的 Profile 是需要严格校验的用户资料。调用 Decoder.DisallowUnknownFields 后,Envelope 和 Profile 的未知键都会被拒绝;而不调用它时,两层都会按默认规则忽略未知键。标准库没有提供“只对某个 JSON 路径开启”的开关,所以需要把严格范围缩小到 Profile。
package main
import (
"encoding/json"
"fmt"
"strings"
)
type Envelope struct {
Profile Profile `json:"profile"`
}
type Profile struct {
DisplayName string `json:"display_name"`
}
func main() {
input := `{"trace_id":"new-field","profile":{"display_name":"Lin","nicknmae":"typo"}}`
var req Envelope
dec := json.NewDecoder(strings.NewReader(input))
// 外层不打开严格模式,兼容未来新增的 trace_id 等字段。
if err := dec.Decode(&req); err != nil {
fmt.Println(err)
}
}
上面的代码在默认规则下不会因为 trace_id 或 nicknmae 报错。注意这里故意把 nickname 写成了 nicknmae,它正是严格校验希望尽早发现的请求错误。
在嵌套类型内部开启 DisallowUnknownFields
让 Profile 自己决定解码策略即可。UnmarshalJSON 中声明一个新的类型 plainProfile,它拥有相同字段但没有 Profile 的方法集,再把数据解码到这个别名值里。否则直接解码到 Profile 会再次调用 UnmarshalJSON,形成递归。
package main
import (
"bytes"
"encoding/json"
)
type Envelope struct {
Profile Profile `json:"profile"`
}
type Profile struct {
DisplayName string `json:"display_name"`
Age int `json:"age"`
}
func (p *Profile) UnmarshalJSON(data []byte) error {
// 别名类型保留字段和 json 标签,但不会再次进入本方法。
type plainProfile Profile
strict := json.NewDecoder(bytes.NewReader(data))
// 严格策略只属于 Profile,不会影响 Envelope 的其他字段。
strict.DisallowUnknownFields()
var value plainProfile
if err := strict.Decode(&value); err != nil {
// 错误会保留 json: unknown field "..." 这类定位信息。
return err
}
*p = Profile(value)
return nil
}
外层仍然这样解码:
var req Envelope
dec := json.NewDecoder(strings.NewReader(`{"trace_id":"v2","profile":{"display_name":"Lin","nicknmae":"typo"}}`))
// 不在这里调用 DisallowUnknownFields,保持外层字段向前兼容。
if err := dec.Decode(&req); err != nil {
fmt.Println(err) // json: unknown field "nicknmae"
}
解码器进入 Profile 字段时会调用它的 UnmarshalJSON,因此 nicknmae 被拒绝;trace_id 在 Envelope 中没有对应字段,却仍按默认规则被忽略。Profile 内部继续嵌套普通 struct 时,严格 Decoder 会沿着这次结构解码继续检查。

数组、map 和 RawMessage 的校验边界

这套方式按目标字段的实际类型生效。数组中的元素如果是 []Profile,每个对象都会进入 Profile.UnmarshalJSON;如果是 []map[string]any,map 本身没有“未声明字段”,因此不存在未知字段错误。json.RawMessage 也只是暂存原始 JSON,只有后续把它交给严格 Decoder 时,校验才会发生。
| 字段形态 | 局部严格校验结果 | 处理建议 |
|---|---|---|
Profile | 拒绝未知键 | 实现 UnmarshalJSON |
[]Profile | 逐个拒绝未知键 | 保证元素类型仍是严格类型 |
map[string]any | 没有未知键概念 | 按业务白名单另行检查 |
json.RawMessage | 暂不检查 | 后续显式解码并选择策略 |
还要留意自定义解码方法的覆盖范围:只要某处目标类型是 Profile,这套严格规则就会被复用。如果同一结构在配置读取时允许扩展、在 HTTP 请求时不允许扩展,建议拆成两个用途明确的类型或包装类型,不要在方法里根据调用方猜测策略。
上线前检查这几个细节
第一,字段必须使用正确的导出名和 json 标签;被标记为 json:"-" 的字段本来就不会参与匹配。第二,未知字段错误通常先报告遇到的一个键,如果接口需要一次返回全部错误,应在解码后增加专门的字段白名单校验。第三,DisallowUnknownFields 解决的是字段边界,不会自动检查必填、取值范围或业务状态,这些仍应交给请求校验层。
常见问题
能不能只对 profile.address 再严格一层?
可以,让 Address 也实现自己的 UnmarshalJSON,或在 Profile 内部把它交给独立严格 Decoder。关键是每个严格边界都要有明确的目标类型。
为什么不用 json.Unmarshal 直接完成?
json.Unmarshal 没有开启 DisallowUnknownFields 的参数。需要严格策略时,应创建 json.Decoder 并调用该方法。
以后想让整个请求都严格怎么办?
可以在最外层 Decoder 上调用 DisallowUnknownFields,但这会改变兼容策略。建议把它作为明确的接口版本或灰度配置,而不是悄悄替换现有行为。
-
860 收藏
-
843 收藏
-
826 收藏
-
809 收藏
-
792 收藏
-
484 收藏
-
Golang · Go教程 | 15小时前 | 类型断言 · Go教程 · encoding/json · JSON解析 · Go JSON解析 json.Decoder UseNumber json.Number263 收藏
-
Golang · Go教程 | 15小时前 | 数据类型 · Go教程 · JSON解析 · 精度处理 · Go JSON解析 float64 json.Decoder UseNumber json.Number427 收藏
-
499 收藏
-
105 收藏
-
331 收藏
-
326 收藏
-
Golang · Go教程 | 16小时前 | 切片 · csv · Go教程 · encoding/csv · 异步处理 · Go encoding/csv 切片复制 CSV读取 ReuseRecord394 收藏
-
155 收藏
-
Golang · Go教程 | 16小时前 | 标准库 · 文件读取 · Go教程 · 错误排查 · CSV解析 · Go ReadAll read encoding/csv FieldsPerRecord ErrFieldCount315 收藏
-
193 收藏
-
118 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习