小到配置文件、中等规模数据文件(100MB)用os.ReadFile+json.Unmarshal最简;超大文件或需流式解析时用json.NewDecoder;动态键名优先map[string]interface{};字段必须首字母大写并加json标签。

用 os.ReadFile + json.Unmarshal 是最简方案
小到配置文件、中等规模的数据文件(os.ReadFile,它一步到位返回 []byte,避免手动开文件、defer、错误传播的样板代码。
-
json.Unmarshal原生支持[]byte,**千万别先转成string再传进去**——多一次内存拷贝,且对含 Unicode 的 JSON 没额外好处 - 结构体字段必须首字母大写(导出),否则
json包静默跳过,字段始终为零值 - 务必显式加
json:"field_name"标签,尤其当 JSON 用蛇形命名(如db_host)而 Go 习惯驼峰(DBHost)时,不加标签会解析失败
data, err := os.ReadFile("config.json")
if err != nil {
log.Fatal(err)
}
var cfg struct {
DBHost string `json:"db_host"`
DBPort int `json:"db_port"`
}
if err := json.Unmarshal(data, &cfg); err != nil {
log.Fatal("parse error:", err)
}
嵌套字段缺失?用指针或 json.RawMessage 区分“空”和“不存在”
JSON 中某个对象字段可能压根没出现({"user":{...}}),也可能显式为 null({"user":null}),还可能为空对象({"user":{}})。如果结构体字段是非指针类型(如 User User),这三种情况在 Go 里都会变成零值结构体,业务逻辑无法区分。
- 用
*User:字段为nil表示 JSON 中该 key 缺失或值为null;非nil才真正有数据 - 用
json.RawMessage:保留原始字节,延迟解析,适合字段类型不确定(比如"data"有时是对象、有时是字符串) - 别依赖
omitempty判断是否存在——它只控制序列化输出,不影响反序列化行为
大文件(>100MB)或流式场景?换 json.NewDecoder
当 JSON 文件体积很大,或者你只想解析其中一部分(比如一个巨型数组里的每个元素),json.Unmarshal 会把整个文件加载进内存,容易触发 OOM。
-
json.NewDecoder基于io.Reader,边读边解析,内存占用恒定 - 适用于日志导入、ETL 脚本、API 流式响应体等场景
- 注意:它一次只解一个顶层 JSON 值(如一个对象或一个数组),如果文件是纯 JSON 数组,需配合
Token()或封装循环解析逻辑
f, _ := os.Open("huge.json")
decoder := json.NewDecoder(f)
var item MyItem
for decoder.Decode(&item) == nil {
// 处理每个 item
}
动态键名或结构不确定?优先选 map[string]interface{},不是 interface{}
当你不知道 JSON 的 key 名(比如监控指标带时间戳前缀),或字段类型混杂("value" 有时是数字、有时是字符串),硬写结构体会很痛苦。
立即学习“go语言免费学习笔记(深入)”;
- 直接解到
map[string]interface{},比先解到interface{}再断言成map少一层 panic 风险 - 数组对应
[]interface{},数字默认是float64,需要int(v.(float64))转换(注意精度) - 若后续还要序列化回去,确保所有值类型都符合 JSON 规范(如不能含
func、channel)
最容易被忽略的其实是字段导出规则和标签一致性——90% 的“解析不出来”问题,根源都在结构体字段小写了,或者漏了 json:"xxx"。别猜,打开文件用 jq . 看一眼原始结构,再对齐写结构体,比反复试错快得多。










