Go SJSON 怎么把已有 JSON 对象直接写入字段
来源:17golang原创
时间:2026-10-13 16:12:40 435浏览 收藏
我第一次把一段已经序列化好的配置对象嵌进另一个 JSON 时,踩到的坑很简单:把对象文本传给 sjson.Set,结果目标字段得到的是一串带引号的字符串,而不是 JSON 对象。Go SJSON 的正确入口是 SetRaw;如果手里本来就是字节,则用 SetRawBytes。
官方地址:https://github.com/tidwall/sjson
这两个函数的语义是“把预序列化 JSON 当作原始块写入”。但原始写入也意味着校验责任更靠近调用方:来源不可信时,先用标准库确认片段合法,再写入目标字段。
先分清 Set 和 SetRaw 的边界
Set 接收普通 Go 值,会按值类型进行 JSON 表示;SetRaw 接收的是已经准备好的 JSON 文本。两者最容易混淆的地方,是“JSON 对象文本”在 Go 里仍然只是 string。
package main
import (
"fmt"
"github.com/tidwall/sjson"
)
func main() {
base := `{"event":"created"}`
objectText := `{"id":17,"enabled":true}`
// Set 会把 string 当成 JSON 字符串,因此对象文本会带引号。
quoted, err := sjson.Set(base, "payload", objectText)
if err != nil {
panic(err)
}
// SetRaw 把已经序列化好的对象作为 JSON 原始块写入。
embedded, err := sjson.SetRaw(base, "payload", objectText)
if err != nil {
panic(err)
}
fmt.Println(quoted)
fmt.Println(embedded)
}
前者的 payload 是字符串,后者的 payload 才是对象。这里的保护资产不是“字符串有没有内容”,而是下游是否还能按对象字段读取。

用 SetRaw 把已有对象写入嵌套字段
目标路径仍然沿用 SJSON 的点号路径。比如要把对象写进 request.meta,只需要把完整对象文本放在第三个参数;如果路径无效,函数会返回错误,调用方应保留原始输入并停止继续组装。
package main
import (
"encoding/json"
"fmt"
"github.com/tidwall/sjson"
)
func main() {
base := []byte(`{"request":{"name":"sync"}}`)
meta := []byte(`{"trace_id":"t-17","labels":{"tier":"edge"}}`)
// 原始片段来自外部或缓存时,先确认它是完整 JSON。
if !json.Valid(meta) {
panic("meta 不是合法 JSON")
}
// SetRawBytes 保留字节路径,避免先把整个文档转成 string。
result, err := sjson.SetRawBytes(base, "request.meta", meta)
if err != nil {
panic(err)
}
fmt.Println(string(result))
}
SetRawBytes 适合请求体、文件或缓存中本来就是 []byte 的场景。不要把“调用成功”误当成“原始片段一定可信”:SJSON 文档说明它期待输入 JSON 已经格式正确,校验和来源判断要由业务层完成。
原始写入前,先画清输入信任边界
如果片段来自同一进程里刚由 json.Marshal 产生的结果,结构风险通常较低;如果来自 HTTP 请求、缓存、消息队列或用户可编辑配置,就应把它视为不可信输入。最小控制是 json.Valid,随后再检查业务上允许的顶层类型和字段范围。
func attachFragment(base, fragment []byte, path string) ([]byte, error) {
// 空路径会让目标字段不明确,先拒绝而不是猜测。
if path == "" {
return nil, fmt.Errorf("目标路径不能为空")
}
// 这里只检查 JSON 语法;权限和字段白名单仍属于业务规则。
if !json.Valid(fragment) {
return nil, fmt.Errorf("原始片段不是合法 JSON")
}
// 原始写入不再替调用方把 string 重新编码成 JSON 字符串。
return sjson.SetRawBytes(base, path, fragment)
}
这个边界可以分成三档:内部可信对象可以直接走 SetRawBytes;外部但只要求语法正确的片段先做 json.Valid;涉及权限、字段白名单或敏感配置的片段,还要在写入前做业务级筛选,不能只依赖语法校验。

什么时候不要用 SetRaw
如果你手里是一个普通结构体、整数、布尔值或明确的 map,优先使用 Set,让 SJSON 负责编码。只有在对象已经序列化、需要嵌入现成 JSON,或必须保留原始 JSON 结构时才使用 SetRaw。这样可以把“谁负责编码”讲清楚,也能减少把未校验字符串当对象写入的机会。
| 输入形态 | 建议 API | 需要额外注意 |
|---|---|---|
| 结构体、数字、布尔值、map | Set | 由库按普通值编码 |
| 已有 JSON 文本 | SetRaw | 写入前检查来源和语法 |
| 已有 JSON 字节 | SetRawBytes | 避免无意义的 string 转换 |
| 不可信的可编辑片段 | 校验后再写入 | 语法校验不等于权限校验 |
把路径和来源写进审计记录
原始 JSON 的问题往往不是“接口报错”,而是某个字段后来被当成字符串或错误对象使用。工程上建议至少记录目标路径、片段来源、校验结果和 SJSON 返回的错误;不要把完整敏感片段直接写进日志。
type RawWriteAudit struct {
Path string
Source string
ValidJSON bool
ErrorText string
}
func auditError(path, source string, err error) RawWriteAudit {
// 记录错误文本和来源标签,避免把可能含敏感数据的原文写入日志。
record := RawWriteAudit{Path: path, Source: source, ValidJSON: false}
if err != nil {
record.ErrorText = err.Error()
}
return record
}
发布前的快速检查清单
- 第三个参数是普通 Go 值,还是已经序列化的 JSON 文本/字节?
- 需要对象结构时,是否明确使用了
SetRaw或SetRawBytes? - 原始片段来自外部边界时,是否先做了
json.Valid和业务字段检查? - 路径、来源标签和错误是否可被审计,但没有泄露完整敏感 JSON?
相关问题:
- SJSON 的 Set 为什么会把已有对象变成字符串?
- JSON 字节已经在内存里时,为什么优先选择 SetRawBytes?
- json.Valid 通过后,还需要做哪些业务级校验?
-
321 收藏
-
450 收藏
-
413 收藏
-
351 收藏
-
293 收藏
-
334 收藏
-
246 收藏
-
174 收藏
-
119 收藏
-
405 收藏
-
311 收藏
-
111 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习