Go语言中json.RawMessage怎么用才能避免嵌套JSON解码陷阱

来源:程序开发作者:沙月恵奈‌头衔:网络博主
导读:本期聚焦于小伙伴创作的《Go语言中json.RawMessage怎么用才能避免嵌套JSON解码陷阱》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《Go语言中json.RawMessage怎么用才能避免嵌套JSON解码陷阱》有用,将其分享出去将是对创作者最好的鼓励。

在Go语言的JSON处理场景中,嵌套结构的解析是一个高频需求,比如接口返回的JSON中包含动态结构的字段,或者嵌套的子结构类型需要根据父结构的其他字段判断。标准的json.Unmarshal方法在解码时会一次性将所有字段解析为对应的Go类型,遇到嵌套动态结构时很容易出现类型不匹配的问题,而json.RawMessage就是专门应对这类场景的标准库类型。

Go语言中json.RawMessage怎么用才能避免嵌套JSON解码陷阱

json.RawMessage是什么

json.RawMessage是Go语言encoding/json包中定义的一个类型,本质是[]byte的别名,它的作用是保留JSON原始编码的字节序列,不会在第一次解码时就将其转换为对应的Go结构。这意味着我们可以先解码外层结构,拿到嵌套字段的原始字节,再根据业务逻辑决定后续的解码方式,避免一次性解码带来的问题。

核心特性

  • 本身是[]byte类型,存储的是未解码的JSON原始数据
  • 实现了json.Marshaler和json.Unmarshaler接口,能够正常参与JSON的编解码流程
  • 支持延迟解码,只有在显式调用json.Unmarshal时才会解析内部内容

常见的嵌套JSON解码陷阱

我们先看一个没有使用json.RawMessage时容易出现的陷阱场景,假设我们有如下嵌套JSON数据,其中data字段的类型是动态的,可能是用户信息也可能是商品信息:

package main

import (
	"encoding/json"
	"fmt"
)

// 错误的定义方式,试图用固定结构接收动态字段
type WrongResponse struct {
	Type string `json:"type"`
	Data interface{} `json:"data"` // 用interface{}接收,解码后类型难以处理
}

func main() {
	// 场景1:data是用户信息
	jsonStr1 := `{"type":"user","data":{"name":"张三","age":20}}`
	// 场景2:data是商品信息
	jsonStr2 := `{"type":"product","data":{"title":"手机","price":3999}}`

	var r1 WrongResponse
	json.Unmarshal([]byte(jsonStr1), &r1)
	// 此时r1.Data的类型是map[string]interface{},取值需要类型断言,容易出错
	fmt.Printf("用户年龄类型:%Tn", r1.Data.(map[string]interface{})["age"])
}

上面的代码存在两个问题,一是用interface{}接收动态字段后,取值需要做多次类型断言,容易引发panic;二是如果data字段的结构非常复杂,一次性解码成map[string]interface{}会损耗性能,还可能丢失部分类型信息。

json.RawMessage的正确使用方式

使用json.RawMessage可以完美解决上面的问题,我们重新定义结构,将data字段声明为json.RawMessage类型:

package main

import (
	"encoding/json"
	"fmt"
)

// 外层响应结构,data字段用json.RawMessage接收原始字节
type Response struct {
	Type string          `json:"type"`
	Data json.RawMessage `json:"data"`
}

// 用户结构
type User struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}

// 商品结构
type Product struct {
	Title string `json:"title"`
	Price int    `json:"price"`
}

func main() {
	jsonStr1 := `{"type":"user","data":{"name":"张三","age":20}}`
	jsonStr2 := `{"type":"product","data":{"title":"手机","price":3999}}`

	// 第一步:解码外层结构,拿到data的原始字节
	var r1 Response
	json.Unmarshal([]byte(jsonStr1), &r1)
	var r2 Response
	json.Unmarshal([]byte(jsonStr2), &r2)

	// 第二步:根据type字段判断data的类型,再进行二次解码
	if r1.Type == "user" {
		var user User
		// 对RawMessage存储的原始字节进行解码
		json.Unmarshal(r1.Data, &user)
		fmt.Printf("用户姓名:%s,年龄:%dn", user.Name, user.Age)
	}

	if r2.Type == "product" {
		var product Product
		json.Unmarshal(r2.Data, &product)
		fmt.Printf("商品名称:%s,价格:%dn", product.Title, product.Price)
	}
}

上面的代码执行后,会正确输出用户和商品的信息,整个过程没有类型断言,也不会出现解码类型不匹配的问题。核心逻辑是先通过json.RawMessage保留data字段的原始JSON数据,再根据外层type字段的提示,选择对应的结构进行二次解码,完全规避了动态嵌套字段的解码陷阱。

使用注意事项

1. 避免重复解码

json.RawMessage的原始数据只应该解码一次,多次解码不仅浪费性能,还可能因为原始数据被修改导致解码失败。如果需要在多个地方使用解码后的结果,应该先将解码后的结构保存下来,而不是反复对RawMessage做Unmarshal。

2. 空值与nil的区别

如果JSON中对应字段是null,那么json.RawMessage的值会是[]byte("null"),而不是nil。在判断字段是否存在或者是否为空时,需要同时判断这两种情况:

func checkRawMessage(raw json.RawMessage) bool {
	if raw == nil || string(raw) == "null" {
		return false
	}
	return true
}

3. 编码时的处理

当把包含json.RawMessage的结构重新编码为JSON时,RawMessage会直接将其存储的原始字节写入结果,不需要额外处理。但如果RawMessage中存储的不是合法的JSON,编码时会返回错误:

func testMarshal() {
	r := Response{
		Type: "test",
		Data: json.RawMessage(`{"key":"value"}`), // 合法JSON
	}
	b, err := json.Marshal(r)
	if err != nil {
		fmt.Println("编码错误:", err)
		return
	}
	fmt.Println(string(b)) // 输出{"type":"test","data":{"key":"value"}}
}

适用场景总结

json.RawMessage适合以下场景:

  • JSON中包含动态类型字段,需要根据其他字段判断解码目标类型
  • 嵌套结构非常复杂,希望延迟解码减少不必要的性能损耗
  • 需要保留原始JSON数据,后续可能需要做二次校验或者转发

只要在处理嵌套JSON时遵循先解码外层、再按需解码内层的逻辑,就能充分利用json.RawMessage的特性,避免嵌套JSON解码过程中的各类陷阱。

json.RawMessageGo语言JSON解码嵌套JSON修改时间:2026-07-22 02:09:32

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。