当前位置:首页 > 文章列表 > Golang > Go教程 > Go json.Decoder 如何只拒绝嵌套对象中的未知字段

Go json.Decoder 如何只拒绝嵌套对象中的未知字段

来源:17golang原创 2026-09-11 09:26:30 0浏览 收藏

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 后,EnvelopeProfile 的未知键都会被拒绝;而不调用它时,两层都会按默认规则忽略未知键。标准库没有提供“只对某个 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_idnicknmae 报错。注意这里故意把 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_idEnvelope 中没有对应字段,却仍按默认规则被忽略。Profile 内部继续嵌套普通 struct 时,严格 Decoder 会沿着这次结构解码继续检查。

Go json.Decoder 外层 Envelope 与嵌套 Profile 的严格校验边界关系图
图1:查看 Envelope 兼容边界与 Profile 严格边界的分组关系,理解未知字段策略为何只在嵌套对象内生效。

数组、map 和 RawMessage 的校验边界

帮助读者判断不同目标类型是否拥有可执行的未知字段边界。
图2:对照结构体、动态容器与延迟校验边界,判断数组、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,但这会改变兼容策略。建议把它作为明确的接口版本或灰度配置,而不是悄悄替换现有行为。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go interface nil 类型断言失败时 comma-ok 与 panic 有何区别Go interface nil 类型断言失败时 comma-ok 与 panic 有何区别
上一篇
Go interface nil 类型断言失败时 comma-ok 与 panic 有何区别
LiblibAI AI画图怎么给客户报价?按草图、修改轮次和交付尺寸拆分
下一篇
LiblibAI AI画图怎么给客户报价?按草图、修改轮次和交付尺寸拆分
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    80次使用
  • SuperCLUE中文大模型评测基准:功能、能力维度与应用指南
    SuperCLUE
    SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
    237次使用
  • C-Eval中文评测基准:大语言模型多学科能力评估指南
    C-Eval
    深入了解C-Eval中文评估套件,涵盖52个学科与4级难度。本文详解其功能特点、Zero-shot/Few-shot使用方法及代码示例,助您全面评测LLM中文理解与泛化能力。
    163次使用
  • AI Prompt Library:免费AI提示词库,助力ChatGPT高效创作与营销
    AI Prompt Library
    探索AI Prompt Library免费资源库,涵盖营销、写作及多场景AI提示词。兼容ChatGPT、Claude等工具,一键复制优化输出,提升工作效率。
    96次使用
  • Generrated:DALL·E 2/3 AI绘画提示词灵感库与图像对比平台
    Generrated
    Generrated汇集9300+张DALL·E生成图像及对应提示词,支持查看完整图集、对比DALL·E 2与3版本差异,是AI绘图新手学习Prompt设计与获取创作灵感的实用工具。
    69次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码