
本文介绍使用 json.rawmessage 将 json 按键(section)拆分为原始字节片段,再针对不同 section 类型精准反序列化为对应结构体的完整实践方案。
本文介绍使用 json.rawmessage 将 json 按键(section)拆分为原始字节片段,再针对不同 section 类型精准反序列化为对应结构体的完整实践方案。
在 Go 中处理动态结构的 JSON 消息(如含多个异构 section 的配置或消息体)时,常见的痛点是:既不想写冗长的 map[string]interface{} + 类型断言 + 手动赋值,也无法直接对嵌套对象调用 json.Unmarshal——因为标准 Unmarshal 只接受完整 JSON 字节流或预定义结构。
幸运的是,Go 标准库提供了 json.RawMessage 这一关键类型:它能延迟解析,将某段 JSON 原样捕获为未解析的 []byte,从而支持“按需解码”。其核心思路是:先以 map[string]json.RawMessage 解析顶层键值对,再对每个 RawMessage 单独反序列化为目标结构体。
以下为完整可运行示例:
package main
import (
"encoding/json"
"fmt"
)
type Book struct {
Author string `json:"Author"`
Title string `json:"Title"`
Price float64 `json:"Price"`
}
type Movie struct {
Title string `json:"Title"`
Year int `json:"Year"` // 改为 int 更符合语义(原示例为 float64)
Stars float64 `json:"Stars"`
Format string `json:"Format"`
}
func main() {
incomingMsg := `{
"Book": {
"Author": "Jack Kerouac",
"Title": "On the Road",
"Price": 5.99
},
"Movie": {
"Title": "Sherlock Holmes vs. the Secret Weapon",
"Year": 1940,
"Stars": 2.5,
"Format": "DVD"
}
}`
// 第一步:用 map[string]json.RawMessage 解析顶层,保留各 section 的原始 JSON 字节
var sections map[string]json.RawMessage
if err := json.Unmarshal([]byte(incomingMsg), §ions); err != nil {
panic(err)
}
// 第二步:遍历每个 section,根据 key 分支反序列化
for sectionName, rawJSON := range sections {
switch sectionName {
case "Book":
var book Book
if err := json.Unmarshal(rawJSON, &book); err != nil {
fmt.Printf("Failed to unmarshal Book: %v\n", err)
continue
}
fmt.Printf("Parsed Book: %+v\n", book)
case "Movie":
var movie Movie
if err := json.Unmarshal(rawJSON, &movie); err != nil {
fmt.Printf("Failed to unmarshal Movie: %v\n", err)
continue
}
fmt.Printf("Parsed Movie: %+v\n", movie)
default:
fmt.Printf("Unknown section: %s (skipped)\n", sectionName)
}
}
}
✅ 优势说明:
- 零字符串拼接/切片:避免手动解析 JSON 字符串边界(易出错且性能差);
- 强类型安全:每个 section 都严格绑定到对应 struct,编译期检查字段名与类型;
- 扩展性好:新增 section 类型只需在 switch 中添加分支,无需改动解析逻辑;
- 内存友好:json.RawMessage 本质是 []byte 切片,不额外复制数据。
⚠️ 注意事项:
- json.RawMessage 必须作为 map 的 value 类型(不可用于 struct 字段除非明确标注 json:",raw"),否则会触发“cannot unmarshal object into Go value”错误;
- 若 section 名称来自外部不可信输入,建议用 map[string]json.RawMessage + 白名单校验,而非 switch(可改用 map[string]func([]byte)error 注册解析器);
- 对于超大 JSON,RawMessage 仍会保留在内存中,若仅需部分字段,可考虑结合 json.Decoder 流式解析。
总结:json.RawMessage 是 Go 处理“半结构化 JSON”的利器。它架起了“泛化解析”与“强类型反序列化”之间的桥梁——既免去了手写类型转换的繁琐,又规避了 interface{} 带来的运行时风险。掌握这一模式,能让你的 JSON 处理代码更健壮、更可维护。











