
本文介绍使用 json.RawMessage 分阶段解析结构不统一的 JSON 数组(如同时含对象和数组),避免 interface{} 类型断言失败,实现类型安全、可维护的反序列化方案。
本文介绍使用 json.rawmessage 分阶段解析结构不统一的 json 数组(如同时含对象和数组),避免 interface{} 类型断言失败,实现类型安全、可维护的反序列化方案。
在 Go 中处理类似如下结构的 JSON 时,常规的结构体嵌套方式会遇到困难:
{
"id": 10,
"result": [
{ "bundled": true, "type": "RM-J1100" },
[
{ "name": "PowerOff", "value": "AAAAAQAAAAEAAAAvAw==" },
{ "name": "Input", "value": "AAAAAQAAAAEAAAAlAw==" }
]
]
}该 result 字段是一个混合切片:首项为对象(设备元信息),次项为对象数组(红外码列表)。若直接定义 []interface{},虽能成功解码,但后续需手动类型断言,易出错且缺乏编译期保障;而强行用 []struct{} 或泛型切片则违反 JSON 结构实际——二者类型本质不同。
推荐方案:使用 json.RawMessage 延迟解析
json.RawMessage 是 []byte 的别名,它跳过即时解析,将原始 JSON 字节流暂存,待明确上下文后再按需反序列化为具体 Go 类型。这既保留了类型安全性,又规避了 interface{} 的运行时不确定性。
以下是完整、健壮的实现示例:
package main
import (
"encoding/json"
"fmt"
)
type Response struct {
ID int `json:"id"`
RawResult []json.RawMessage `json:"result"` // 暂存原始字节,不解析
Header *Header `json:"-"` // 运行时填充
Values []*Value `json:"-"` // 运行时填充
}
type Header struct {
Bundled bool `json:"bundled"`
Type string `json:"type"`
}
type Value struct {
Name string `json:"name"`
Value string `json:"value"`
}
func (r *Response) ParseResult() error {
if len(r.RawResult) < 2 {
return fmt.Errorf("expected at least 2 items in result array")
}
// 解析第一个元素为 Header
r.Header = &Header{}
if err := json.Unmarshal(r.RawResult[0], r.Header); err != nil {
return fmt.Errorf("failed to unmarshal header: %w", err)
}
// 解析第二个元素为 []*Value
r.Values = []*Value{}
if err := json.Unmarshal(r.RawResult[1], &r.Values); err != nil {
return fmt.Errorf("failed to unmarshal values: %w", err)
}
return nil
}
func main() {
rawJ := []byte(`{
"id": 10,
"result": [
{"bundled": true, "type": "RM-J1100"},
[
{"name": "PowerOff", "value": "AAAAAQAAAAEAAAAvAw=="},
{"name": "Input", "value": "AAAAAQAAAAEAAAAlAw=="}
]
]
}`)
resp := &Response{}
if err := json.Unmarshal(rawJ, resp); err != nil {
panic(err)
}
if err := resp.ParseResult(); err != nil {
panic(err)
}
fmt.Printf("ID: %d\n", resp.ID)
fmt.Printf("Header: %+v\n", *resp.Header)
fmt.Printf("Values (%d): %+v\n", len(resp.Values), resp.Values)
}✅ 优势说明:
-
类型安全:
Header和Values字段均为强类型,IDE 可自动补全,编译器可校验字段访问; - 错误隔离:每个子解析独立报错,便于定位问题来源(如某段 JSON 格式错误仅影响对应模块);
-
可扩展性强:新增
result第三项(如metadata对象)只需追加json.Unmarshal(r.RawResult[2], &r.Metadata)即可; -
零反射开销:相比
map[string]interface{}+json.Marshal/json.Unmarshal循环转换,性能更优。
⚠️ 注意事项:
-
json.RawMessage会复制原始字节,对超大 JSON 需注意内存占用; - 必须确保
RawResult索引顺序稳定(如本例中[0]恒为Header,[1]恒为Values),否则应先检查RawResult[0][0]是否为{或[来动态判断; - 生产环境务必添加完整的错误处理(如上例中
ParseResult()的显式错误返回),不可忽略json.Unmarshal的返回值。
综上,面对非均匀 JSON 数组,json.RawMessage 是 Go 官方推荐、社区广泛验证的最佳实践——它用少量代码换来了清晰的控制流、可靠的类型系统支持与长期可维护性。


















