Go encoding/json 怎样保留未知字段
来源:17golang原创
时间:2026-10-14 01:11:41 412浏览 收藏
Go encoding/json 要保留未知字段,最直接的做法是把 JSON 对象解码到 map[string]json.RawMessage;需要访问固定字段时,再把已知字段与扩展字段分开,并在重新编码时合并。仅解码到普通 struct 后再编码,未映射的字段不会自动回来。
官方地址:https://pkg.go.dev/encoding/json
只改名称,却不能丢掉调用方扩展的数据
假设一个配置中转服务只负责修改 name。调用方还可能传入 trace、meta 和 optional,服务不理解这些字段,却必须把它们交还给下一环节。这不是“忽略未知字段”就能解决的任务:忽略意味着它们不进入结构体,透传则要求它们仍有存放位置。
这里的目标是保留字段值的 JSON 表示,而不是逐字节复制整个请求。字段顺序、空白和转义可能在重新编码时变化;若请求签名依赖完整原始字节,应另存原始请求,不能用重新编码的结果代替。
三个容器方案怎么选
| 方案 | 适用任务 | 需要承担的工作 |
|---|---|---|
| 普通 struct | 固定协议,允许舍弃未定义字段 | 没有未知字段存储位置 |
| map[string]any | 需要遍历并修改所有层级的动态数据 | 类型断言;数字需考虑 Decoder.UseNumber |
| map[string]json.RawMessage | 少量字段解析,其余原样透传 | 对要访问的字段单独解码 |
| 固定字段加 Extra | 业务层需要固定字段,同时兼容扩展 | 自定义解码和编码,约定冲突策略 |
本例选择最后一种组织方式,底层仍使用 RawMessage。Name 是业务字段,Extra 是扩展容器。trace 的数字文本、meta 的对象和 optional 的 null 都作为 RawMessage 保存,不需要提前转换成 float64。

用临时对象拆分,再合并回 JSON
下面是完整原创示例。它明确要求顶层为对象,采用大小写敏感的 name 键,并使用临时对象避免错误解码破坏已有 Record。代码是可供读者复现的示例,不是本机运行记录。
package main
import (
"encoding/json"
"fmt"
)
type Record struct {
Name string
Extra map[string]json.RawMessage
}
func (r *Record) UnmarshalJSON(data []byte) error {
// 先解码到临时对象,失败时不改动原对象;本接口只接受 JSON 对象。
var fields map[string]json.RawMessage
if err := json.Unmarshal(data, &fields); err != nil { return err }
if fields == nil { return fmt.Errorf("需要 JSON 对象") }
next := Record{Extra: make(map[string]json.RawMessage)}
for key, value := range fields {
// 采用严格的规范字段名,其他键保留原来的 JSON 值。
if key == "name" {
if err := json.Unmarshal(value, &next.Name); err != nil {
return fmt.Errorf("name: %w", err)
}
} else {
next.Extra[key] = value
}
}
*r = next
return nil
}
func (r Record) MarshalJSON() ([]byte, error) {
// 新建容器,不修改 Extra;已知字段始终覆盖同名扩展字段。
fields := make(map[string]json.RawMessage, len(r.Extra)+1)
for key, value := range r.Extra { fields[key] = value }
name, err := json.Marshal(r.Name)
if err != nil { return nil, err }
fields["name"] = name
return json.Marshal(fields)
}
func main() {
// 演示修改已知字段,同时透传未知的大整数、嵌套对象和 null。
input := []byte(`{"name":"初稿","trace":9007199254740993,"meta":{"region":"east"},"optional":null}`)
var r Record
if err := json.Unmarshal(input, &r); err != nil { panic(err) }
r.Name = "定稿"
output, err := json.Marshal(r)
if err != nil { panic(err) }
fmt.Println(string(output))
}
输入中的 trace 故意超过常见浮点数能够精确表示连续整数的范围。这里不把它解码成浮点数,所以透传时不会走浮点转换。若业务需要计算 trace,仍应另行解码到合适的整数类型并处理溢出。
重新编码时,fields 是新 map。Extra 中即使被业务代码加入 name,也会被当前 Name 覆盖;这让修改名称的职责清楚。其他扩展值如果被人为写成非法 JSON,Marshal 会报错,调用方必须处理,不能忽略错误直接发送响应。
字段名、缺失与 null 要先约定
该示例的 name 与 NAME 是不同键:前者归业务字段,后者保留在 Extra。这样处理是本例的接口选择,不是声称普通 struct 解码也区分大小写。如果接入协议要求忽略大小写,就必须对所有匹配变体统一处理并制定重复键策略,不能只删除小写 name。
示例不会区分 name 缺失、显式 null 与空字符串,它们最终都可能表现为空名称。如果业务必须区分这三种状态,应额外记录“键是否存在”和“是否为 null”,或者保留 name 的 RawMessage 后按需解析。
重复键也不属于 map 方案的保真范围。同一键不能在 map 内保存多个出现位置;若业务必须保留重复成员与原始顺序,需要面向 token 的对象表示或保存完整字节,不能把 map 包装成无损文档编辑器。

哪些任务不适合这套写法
如果接口必须拒绝新增字段,应使用明确协议检查,例如面向 struct 的 DisallowUnknownFields,而不是悄悄把所有字段收入 Extra。如果只做完整消息转发,不修改内容,保存原始字节比解码再编码更符合职责。需要频繁修改未知字段内部属性时,则应把相关 RawMessage 进一步解码,而不是拼接 JSON 字符串。
顶层 Extra 只解决顶层未映射字段。若某个已知字段是嵌套 struct,其内部未知成员仍需要单独设计保留机制。本例的 meta 留作 RawMessage,所以内部不被业务结构体筛掉。
选择前的速查与常见疑问
- 只访问固定字段,允许丢扩展:普通 struct。
- 只改几个字段,其余透传:RawMessage map。
- 固定字段要类型约束、扩展要保留:Record 与 Extra。
- 必须保留签名输入的精确字节:另存原始请求。
加一个 Extra 字段就能自动收集未知键吗?
不能。encoding/json 不会把未匹配的顶层成员自动塞入这个 map。本例靠 UnmarshalJSON 显式分配,MarshalJSON 显式合并。
复用同一个 Record 会留下上一次的扩展吗?
本例每次创建新的 next.Extra,并在成功时整体赋值,因此不会混入上次字段。不要改成直接向旧 map 追加而不处理残留成员。
保留未知字段是否表示可以信任它们?
不是。保留只是存储策略,业务仍需要明确可接受的字段、体积和用途;不要把透传字段直接当成服务内部配置。
参考:Go 标准库 encoding/json 文档。本文讨论 encoding/json 的传统接口,不混用 encoding/json/v2 的字段规则。
-
454 收藏
-
211 收藏
-
350 收藏
-
299 收藏
-
364 收藏
-
147 收藏
-
Golang · Go教程 | 13分钟前 | 超时 · go · 性能 · HTTP客户端 · Go net/http HTTP超时 http.Transport ResponseHeaderTimeout133 收藏
-
484 收藏
-
444 收藏
-
Golang · Go教程 | 40分钟前 | 日志 · HTTP · go · Context · slog · 可观测性 · Go context.Context 结构化日志 请求日志 log/slog slog.Logger136 收藏
-
419 收藏
-
136 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习