当前位置:首页 > 文章列表 > Golang > Go教程 > Go json.RawMessage 延迟解析时如何避免共享底层字节

Go json.RawMessage 延迟解析时如何避免共享底层字节

来源:17golang原创 2026-09-14 20:39:28 0浏览 收藏

json.RawMessage 做延迟解析时,最容易误判的是“类型名像字节切片,所以赋值一定安全”。结论很明确:通过 json.Unmarshal 写入 RawMessage 时,标准库会复制输入;但把已有的 []byte 直接转换成 json.RawMessage,或者把一个 RawMessage 直接赋给另一个变量,仍可能共享底层数组。只要原缓冲区还会复用,就应在跨出它的生命周期前复制。

把 RawMessage 留在当前解码对象里通常不需要额外复制;把它交给队列、缓存或另一个 goroutine 时,使用 append([]byte(nil), src...) 建立独立所有权,再做延迟解析。
要点速览
  • UnmarshalJSON 的契约是把输入保存为副本。
  • json.RawMessage(src) 只是切片转换,不是深复制。
  • 跨缓冲区、队列或 goroutine 传递时,在边界处 clone 一次。

先分清标准库复制和手动共享

RawMessage 本质上是 []byte 的命名类型,价值在于先保留一段合法 JSON,等知道业务类型后再调用 json.Unmarshal。官方实现的 UnmarshalJSON 会用 append 把输入复制到接收者,因此下面这种结构可以直接保存原始字段:

type Envelope struct {
    Kind    string          `json:"kind"`
    Payload json.RawMessage `json:"payload"` // 先保存原始 JSON,稍后按 Kind 解析
}

var e Envelope
if err := json.Unmarshal(data, &e); err != nil {
    return err // 输入结构不合法时,不把半成品交给后续业务
}
// e.Payload 已经拥有自己的字节副本,可以脱离 data 延迟解析。

危险点在手动提取路径。view := json.RawMessage(data) 不会逐字节复制,viewdata 仍指向同一块底层数组。若读取器下一轮把新消息写回 data,保存的 view 就可能被悄悄改写。

Go json.RawMessage 解码时 json.Unmarshal 复制输入与手动切片转换共享底层数组的结构示意图
图1:RawMessage 读取阶段的结构示意;标准库反序列化保存副本,手动切片转换仍可能指向原缓冲区。

把复制放在跨生命周期的边界

复制不应到处散落,而应放在“数据即将离开输入缓冲区”的那个位置。一个足够通用的 helper 如下:

func cloneRaw(src []byte) json.RawMessage {
    // append 到 nil 切片会分配新底层数组,避免与 src 共享存储
    dst := append([]byte(nil), src...)
    return json.RawMessage(dst)
}

func retainForQueue(frame []byte) json.RawMessage {
    // 入队后由其他 goroutine 消费,所以这里必须先取得所有权
    return cloneRaw(frame)
}

如果项目最低版本允许,也可以用 bytes.Clone 表达同样的意图。关键不是 helper 的名字,而是让调用者一眼看出返回值不再依赖输入缓冲区。对空切片要保留语义:append([]byte(nil), nil...) 得到 nil,后续 Marshal 时会按 RawMessage 的规则表现为 JSON null;如果业务需要空数组,应明确传入 [],不要用 clone 偷换语义。

Go json.RawMessage 在输入缓冲区、clone、队列和延迟 json.Unmarshal 之间的字节所有权示意图
图2:延迟解析的所有权示意;跨出输入缓冲区生命周期前先复制,异步消费者再独立解析。

延迟解析和并发消费时检查四个节点

第一,解析前不要修改 RawMessage 的内容。它可以作为输入交给 json.Unmarshal,但不要一边交给消费者一边对同一底层数组做原地清洗。第二,切片赋值只是复制三元组(指针、长度、容量),例如 saved := current 不会产生独立数据;要独立保存就调用 cloneRaw(saved)

第三,交给 goroutine 的不是“变量”,而是它引用的字节。即使外层结构体没有再变化,只要来源 buffer 会被复用,仍然可能发生数据竞争或内容漂移。第四,重新编码时 RawMessage.MarshalJSON 会直接返回自身字节,因此它也不替你创建并发安全的快照;需要快照的场景应在进入共享区域前完成复制。

来源能否直接长期保存建议
json.Unmarshal 写入 RawMessage可以标准库已复制输入
json.RawMessage(src)通常不可以先 clone,再入队或缓存
RawMessage 直接赋值不可以按需要复制底层字节
同一 goroutine 内立即解析视来源而定不跨生命周期时可避免多余复制

用一张清单固定项目里的判断

可以把下面四个问题放进 code review:这段字节来自标准库解码还是复用 buffer?RawMessage 会不会进入队列、缓存或 goroutine?持有期间是否有人会原地写入源切片?下游是否需要一个不可变快照?只要前三问出现“会”,或者最后一问是“需要”,就在边界处复制一次。

实践中不要为了“保险”在每次 json.Unmarshal 前后都复制。那会制造额外分配,却没有增加安全性。更稳妥的路线是:标准库解码结果直接使用;手动切片转换先 clone;跨异步或缓存边界时明确转移所有权;完成快照后不再修改其字节。

相关问题

RawMessage 延迟解析是不是一定比直接解析更快?

不一定。它适合先根据类型字段选择目标结构,或只处理部分消息;如果最终所有字段都会解析,延迟本身不会消除解析成本。

RawMessage 和 []byte 直接比较有什么问题?

两者都是字节序列,不能用 == 比较;需要内容比较时使用 bytes.Equal,并先确认双方是否代表同一种 JSON 形式。

复制一次后还能修改 RawMessage 吗?

可以,但应把它视为当前持有者的私有数据。不要在它已经交给其他 goroutine 或编码器使用时原地修改,否则仍会产生数据竞争。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Transformers generate 如何用 stopping criteria 停在自定义标记Transformers generate 如何用 stopping criteria 停在自定义标记
上一篇
Transformers generate 如何用 stopping criteria 停在自定义标记
Python 3.14 free-threaded 构建如何确认解释器模式
下一篇
Python 3.14 free-threaded 构建如何确认解释器模式
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    26次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    130次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    57次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    22次使用
  • OpenCompass大模型评测体系详解:功能、使用指南与应用场景
    OpenCompass
    OpenCompass是上海AI实验室推出的开源大模型评测平台,提供CompassKit、CompassHub和CompassRank三大核心组件,支持LLM及多模态模型的一站式标准化评估与排行榜查询。
    80次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码