Zero-allocation JSON for Go — up to 16.7× faster parsing, SIMD-accelerated scanning (SSE2/AVX2 · NEON), lazy Get path queries, drop-in encoding/json replacement.
零分配 Go JSON 库 — 解析最高快 16.7 倍,SIMD 加速扫描(SSE2/AVX2 · NEON),懒路径查询,兼容 encoding/json。
原为 yak 网络框架内置,现已独立发布。
- 零分配解析 — 所有字符串指向原始 JSON 字节切片,全程零拷贝
- 懒查询 API —
Get(json, "user.name")点路径查询,不构建 DOM - 池化复用 — Parser / Writer 通过
sync.Pool复用,并发安全 - 兼容标准库 — 完整支持
json.Marshaler/json.Unmarshaler接口和 struct tag(含omitempty、omitzero、,string、嵌入字段提升) - 流式序列化 — Writer 直接向
[]byte追加,无中间io.Writer层 - 安全校验 — 最大嵌套深度、最大键长、最大字符串长度、控制字符拒绝,运行时可动态调整
- RFC 8259 解析合规 — 拒绝未转义控制字符(§7)、非法数字格式(§6);
Validate返回含偏移量的描述性错误;StrictNumbers额外拒绝前导零和 float64 溢出(HTML 转义和键排序默认关闭,RFC 不强制,与 sonic 策略一致) []bytebase64 全路径 — Marshal/Unmarshal 均支持[]byte ↔ base64(兼容encoding/json)RawMessage原始嵌入 —RawMessage字段序列化时直接嵌入原始 JSON 片段,Unmarshal 时捕获原始字节MismatchTypeError宽松解析 — 类型不匹配时跳过字段继续,完成后返回最后一个*MismatchTypeErrorValue.Values()/Value.KVs()— 数组元素切片与对象键值对切片(含导出类型KV{Key, Value})- 平台感知 — amd64 SSE2/AVX2 字符串扫描,arm64 NEON,其他平台 SWAR 8 字节并行
- 预编译序列化 — struct 编码计划缓存复用 + 预扩容,避免重复反射和多次 buffer grow
- digit pair 查表 — 整数序列化使用两位数对查表,除法次数减半
与 encoding/json 的对比:
测试环境: Intel Xeon E-2186G @ 3.80GHz, Go 1.25.7, Linux 6.17, amd64
| 基准测试 | yakjson | encoding/json | 加速比 | 分配 |
|---|---|---|---|---|
| Parse | 170 ns/op | 2,847 ns/op | 16.7x | 0 vs 41 |
| Marshal | 127 ns/op | 316 ns/op | 2.49x | 0 vs 1 |
| Unmarshal | 375 ns/op | 1,554 ns/op | 4.15x | 3 vs 14 |
| Writer | 126 ns/op | — | — | 0 |
| 基准测试 | yakjson | encoding/json | 加速比 | 分配 |
|---|---|---|---|---|
| Marshal (Binding) | 16,700 ns/op (1,024 MB/s) | 33,500 ns/op (510 MB/s) | 2.01x | 1 vs 1 |
| Marshal (Generic) | 69,900 ns/op (245 MB/s) | 170,100 ns/op (100 MB/s) | 2.43x | 1 vs 1,270 |
| Unmarshal (Binding) | 34,600 ns/op (494 MB/s) | 190,100 ns/op (90 MB/s) | 5.49x | 24 vs 490 |
| Unmarshal (Generic) | 73,200 ns/op (233 MB/s) | 192,400 ns/op (89 MB/s) | 2.63x | 794 vs 2,039 |
完整测试结果: bench_linux_6c12t.txt
基准测试脚本和 CI 工作流:
# 运行基准测试并保存结果 (bench_{os}_{cores}c{threads}t.txt)
./bench.sh
# 自定义参数: benchtime=5s, count=5
./bench.sh 5s 5
# 性能回归检查(本地对比)
./bench_guard.sh
# 更新当前平台基线
./bench_guard.sh --update-baselineCI 基准测试由 bench.yml 工作流驱动,采用并发矩阵策略:
| 平台 | Runner | 架构 | 汉编路径 |
|---|---|---|---|
ubuntu-latest |
Ubuntu 24.04 | x86_64 | scan_amd64.s SSE2/AVX2 |
windows-latest |
Windows Server 2025 | x86_64 | scan_amd64.s SSE2/AVX2 |
ubuntu-24.04-arm |
Ubuntu 24.04 | aarch64 | scan_arm64.s NEON |
macos-latest |
macOS 15 (Apple M) | aarch64 | scan_arm64.s NEON |
PR 时同机器对比 HEAD 与 base,benchstat 输出差异百分比。结果归档到独立的 bench 分支:
bench 分支/
├── main/ # push to main 后自动更新
│ ├── ubuntu-latest-amd64.txt
│ ├── windows-latest-amd64.txt
│ ├── ubuntu-24.04-arm-arm64.txt
│ └── macos-latest-arm64.txt
└── pr-{N}/ # PR #{N} 触发时写入
└── {os}-{arch}.txt
import "github.com/uniyakcom/yakjson"
// 点分隔路径,零分配,不构建 DOM
theme := json.Get(jsonStr, "user.metadata.preferences.theme").String()
// 数组下标
first := json.Get(jsonStr, "items.0.name").String()
// 数字/布尔
count := json.Get(jsonStr, "total").Int()
active := json.Get(jsonStr, "user.active").Bool()
// 检查存在性
if r := json.Get(jsonStr, "optional.key"); r.Exists() {
fmt.Println(r.String())
}
// 获取原始 JSON
raw := json.Get(jsonStr, "user.address").Raw()var p json.Parser
v, err := p.Parse(`{"name":"yak","version":1,"features":["fast","safe"]}`)
if err != nil {
panic(err)
}
name := v.GetString("name") // "yak"
ver := v.GetInt("version") // 1
feat := v.GetString("features", "0") // "fast"p := json.AcquireParser()
defer json.ReleaseParser(p)
v, err := p.Parse(jsonStr)
// ... 使用 v ...// {"user":{"profile":{"age":25}}}
age := v.GetInt("user", "profile", "age") // 25
// 数组索引
// {"items":[{"id":1},{"id":2}]}
id := v.GetInt("items", "1", "id") // 2// 数组遍历
v.ArrayEach(func(i int, elem *json.Value) bool {
fmt.Printf("[%d] = %s\n", i, elem.GetString())
return true // 返回 false 提前终止
})
// 对象遍历(保持字段顺序)
v.ObjectEach(func(key string, val *json.Value) bool {
fmt.Printf("%s: %v\n", key, val.Type())
return true
})w := json.AcquireWriter()
defer json.ReleaseWriter(w)
w.Object(func(w *json.Writer) {
w.Field("name", "yak")
w.FieldInt("version", 1)
w.FieldBool("stable", true)
w.FieldArray("tags", func(w *json.Writer) {
w.Item("fast")
w.Item("safe")
})
w.FieldObject("config", func(w *json.Writer) {
w.FieldInt("workers", 4)
w.FieldNull("extra")
})
})
data := w.Bytes()
// {"name":"yak","version":1,"stable":true,"tags":["fast","safe"],"config":{"workers":4,"extra":null}}type User struct {
Name string `json:"name"`
Age int `json:"age"`
Email string `json:"email,omitempty"`
Score int `json:"score,omitzero"`
}
// 序列化
data, err := json.Marshal(&User{Name: "yak", Age: 1})
// {"name":"yak","age":1}
// 零分配序列化到已有 buffer
buf, err := json.MarshalTo(existingBuf, &user)
// 反序列化
var u User
err = json.Unmarshal(data, &u)// 修改已有字段
result, _ := json.Set(`{"name":"yak","ver":1}`, "ver", 2)
// {"name":"yak","ver":2}
// 设置嵌套路径(自动创建中间对象)
result, _ = json.Set(`{"a":{}}`, "a.b", "hello")
// {"a":{"b":"hello"}}
// 数组索引
result, _ = json.Set(`{"items":["a","b","c"]}`, "items.1", "B")
// {"items":["a","B","c"]}// 遍历数组元素(零 DOM、流式回调)
json.Each(`[1,"a",true]`, "", func(key string, value json.Res) bool {
fmt.Println(key, value.String()) // "0" 1, "1" a, "2" true
return true // false 终止遍历
})
// 按路径遍历嵌套数组
json.Each(data, "data.users", func(key string, value json.Res) bool {
name := json.Get(value.Raw(), "name").String()
fmt.Println(name)
return true
})
// 遍历对象键值对
json.Each(`{"a":1,"b":2}`, "", func(key string, value json.Res) bool {
fmt.Printf("%s = %d\n", key, value.Int())
return true
})result, err := json.SetMany(`{"a":1,"b":2,"c":3}`,
json.SetOp{Path: "a", Value: 10},
json.SetOp{Path: "b", Value: 20},
json.SetOp{Path: "c", Value: "hello"},
)
// {"a":10,"b":20,"c":"hello"}// MarshalIndent — 格式化输出
data, _ := json.MarshalIndent(v, "", " ")
// Encoder — 流式编码(兼容 encoding/json.Encoder)
enc := json.NewEncoder(w)
enc.SetEscapeHTML(true)
enc.SetIndent("", " ")
enc.Encode(myStruct)
// Decoder — 流式解码(兼容 encoding/json.Decoder)
dec := json.NewDecoder(r)
dec.Decode(&myStruct)
// Decoder.UseNumber — 将 JSON 数字解析为 json.Number(字符串形式),而非 float64 / int64
// 等价于 encoding/json.Decoder.UseNumber()
dec2 := json.NewDecoder(r)
dec2.UseNumber()
var v any
dec2.Decode(&v)
n := v.(json.Number) // 调用 .Int64() / .Float64() / .String()
// Decoder.More + 多值流式解码 — 从同一 Reader 连续解码多个 JSON 值
dec3 := json.NewDecoder(r) // r 含多个 JSON 值,如 `{"a":1}{"b":2}` 或换行分隔的 NDJSON
for dec3.More() {
var item MyStruct
if err := dec3.Decode(&item); err != nil {
break
}
process(item)
}
// Decoder.DisallowUnknownFields — JSON 含 struct 未定义字段时返回错误
dec4 := json.NewDecoder(r)
dec4.DisallowUnknownFields()
var s MyStruct
if err := dec4.Decode(&s); err != nil {
// err: json: unknown field "xxx"
}
// Decoder.Buffered — 返回 Decode 消费后缓冲区中剩余未解析字节的 io.Reader
dec5 := json.NewDecoder(r) // r 含 `{"a":1} {"b":2}`
dec5.Decode(&myStruct) // 消费第一个对象
rest, _ := io.ReadAll(dec5.Buffered()) // rest = ` {"b":2}`
// Decoder.Token — 逐 token 解析 JSON 流(兼容 encoding/json.Decoder.Token)
// 返回值类型: Delim({,},[,])、bool、nil(null)、string、float64 或 Number(UseNumber 启用后)
dec6 := json.NewDecoder(strings.NewReader(`{"k":42}`))
for {
tok, err := dec6.Token()
if err != nil { break } // io.EOF 时结束
switch v := tok.(type) {
case json.Delim:
fmt.Println("delim:", v)
case string:
fmt.Println("key:", v)
case float64:
fmt.Printf("num: %v (offset=%d)\n", v, dec6.InputOffset())
}
}
// Decoder.Reset — 复用已有 Decoder,旧内存落回居第二次解码时差项不分配
// 配置(UseNumber、DisallowUnknownFields)在 Reset 后保留
dec7 := json.NewDecoder(bytes.NewReader(data1))
dec7.UseNumber()
dec7.Decode(&v1)
// ... 处理 v1 ...
dec7.Reset(bytes.NewReader(data2)) // 绑定新 reader,UseNumber 依然有效
dec7.Decode(&v2)
// DecoderPool — 高频并发场景的 *Decoder 池化复用
// 实测(常见60 B JSON):pool 552 B/7 allocs/1.2 µs vs new 2600 B/9 allocs/2.4 µs。
// Get(r) 自动 Reset + 清空 UseNumber/DisallowUnknownFields(配置隔离);
// Put(dec) 将 reader 置 nil 后归还,避免泄漏引用。
pool := json.NewDecoderPool()
// ...
dec8 := pool.Get(bytes.NewReader(data)) // 从池获取并重置
var result MyStruct
_ = dec8.Decode(&result)
pool.Put(dec8) // 归还到池
// Decoder.Detach — 字符串内存独立(default: 零拷贝,opt-in: 安全拷贝)
//
// 默认行为(性能优先):string/Number/map键直接引用 Decoder 内部 buf。
// Decode 后程序内同步处理安全;提前 Put 到 Pool 或 Reset 则不安全。
//
// Detach opt-in:每个无 '\'转义的字符串增加一次拷贝,与 buf 完全解绑。
// 适用场景:Pool 复用 + Decode 后需要异步处理(goroutine)或存入 map/cache。
// 实例 A:单个 Decoder
dec9 := json.NewDecoder(r)
dec9.Detach() // 为当前 decoder 启用
var item MyItem
dec9.Decode(&item)
go asyncProcess(item) // ✅ 安全:item 字符串不依赖 dec9 内部 buf
// 实例 B:DecoderPool 池级启用(推荐)
var safePool = json.NewDecoderPool().Detach() // 链式调用
dec10 := safePool.Get(r)
var item2 MyItem
dec10.Decode(&item2)
safePool.Put(dec10) // 归还后字段依然有效
go asyncProcess(item2) // ✅ 安全
// 实例 C:UseNumber + Detach
dec11 := safePool.Get(r)
dec11.UseNumber() // Get 后按需配置
var m map[string]any
dec11.Decode(&m)
safePool.Put(dec11)
n := m["price"].(json.Number) // ✅ 安全:Number 已是独立副本
// Number 类型 — json.Number 的别名,Marshal 时输出裸 JSON 数字字面量(无引号)
price := json.Number("3.14")
data, _ := json.Marshal(price) // → 3.14 (不是 "3.14")
// MarshalWrite / UnmarshalRead — v2 风格流式 I/O
json.MarshalWrite(w, myStruct)
json.UnmarshalRead(r, &myStruct)
// Valid — JSON 合法性检查(兼容 encoding/json.Valid)
if json.Valid(data) { ... }
// Validate — 返回含位置信息的错误(调试/日志场景)
if err := json.Validate(data); err != nil {
log.Printf("invalid JSON: %v", err) // json: … at offset N
}
// ValidateString — 字符串版本
if err := json.ValidateString(s); err != nil { ... }
// 配置式选项(无全局副作用)
opts := json.MarshalOptions{EscapeHTML: true}
data, _ = opts.Marshal(v)
opts.MarshalWrite(w, v)| 特性 | yakjson | encoding/json |
|---|---|---|
HTML 转义 (<, >, &) |
默认不转义(性能优先),可选 SetOptions(Options{EscapeHTML: true}) |
默认转义 |
| NaN / Inf | 默认输出 null,可选 NaNInfError 模式返回错误 |
返回错误 |
any 数字类型 |
默认整数优先 int64,可选 NumberFloat64Mode / UseNumber / NumberJSONMode |
始终 float64 |
| 浮点格式 | 与 encoding/json 一致:abs ∈ [1e-6, 1e21) 用 f 格式,超出用 e 格式;整数快速路径(IEEE 754 精确范围 ≤2^53)直接用 appendInt;输出后经 strconv.ParseFloat 验证 round-trip,极端次正规数自动回退到 17 位全精度 e 格式;解析器在次正规数区间(< 2.3e-308)自动以 strconv.ParseFloat 兜底,保证精度 |
相同策略 |
json.Number |
Number = json.Number 别名;Marshal 输出裸数字字面量(无引号) |
同 |
Decoder.UseNumber() |
✅ 将 any 字段解析为 Number |
✅ |
Decoder.More() |
✅ 检测缓冲区是否仍有可解码值 | ✅ |
Decoder.Buffered() |
✅ 返回已解码内容后缓冲区剩余字节的 io.Reader |
✅ |
Decoder.DisallowUnknownFields() |
✅ 未知字段返回 "json: unknown field" 错误 |
✅ |
Decoder.Token() |
✅ token 级流式解析,返回 Delim/bool/string/float64/Number/nil;超过 MaxDepth 嵌套层级返回 LimitError{Kind: LimitDepth} |
✅ |
Decoder.InputOffset() |
✅ 返回已消费输入流字节偏移量 | ✅ |
Decoder.Reset(r) |
✅ 绑定新 reader 并清零读取状态,UseNumber 等配置保留 |
❓ 无此 API |
Decoder.Detach() |
✅ opt-in 内存独立:字符串/Number/map键独立分配不引用内部 buf;常锁防止 Pool 复用时的静默损坏。默认零拷贝,结合 DecoderPool.Detach() 使用 |
❓ 无此 API |
DecoderPool |
✅ sync.Pool 封装,Get(r) 全自动 Reset + 配置隔离;Put 释放 reader 引用。Detach() 启用池级内存独立。实测堆内存节省可达 79%(552 B vs 2600 B) |
❓ |
json.Delim |
✅ = encoding/json.Delim 类型别名(结构分隔符 {/}/[/]) |
✅ |
UnmarshalOptions.DisallowUnknownFields |
✅ per-call 配置式忽略/拒绝未知字段 | 需 Decoder 方式 |
Decoder 多值流式解码 |
✅ 同一 Reader 连续 Decode(NDJSON/多 JSON 值) | ✅ |
Encoder 并发安全 |
✅ sync.Mutex 保护并发 Encode;SetEscapeHTML/SetIndent 同样加锁,支持运行时动态配置;indent 路径通过 sync.Pool 复用 *bytes.Buffer 消除堆分配;零值 Encoder{} 安全(懒初始化兜底) |
❓ 文档未明说 |
[]byte 字段 |
Marshal/Unmarshal 双向 base64 ✅ | 双向 base64 |
| struct tag | json:"name,omitempty" / json:"name,omitzero" / json:"name,string" / json:"-" |
相同(Go 1.24+ 支持 omitzero) |
json.Marshaler |
✅ | ✅ |
json.Unmarshaler |
✅ | ✅ |
从 encoding/json 迁移到 yakjson 通常只需更改导入路径,但以下行为差异需要注意:
| 场景 | encoding/json 行为 |
yakjson 行为 / 建议 |
|---|---|---|
any 数字类型 |
始终 float64 |
默认整数优先 int64;设置 NumberFloat64Mode 可得到相同行为 |
| HTML 转义 | 默认转义 </>/& |
默认不转义(性能优先);HTTP 响应场景建议 SetOptions(Options{EscapeHTML:true}) |
| NaN / Inf | 返回错误 | 默认输出 null;设置 NaNInfError 可得到相同行为 |
Encoder 并发 |
文档未明说(实际不安全) | 内置 sync.Mutex,并发 Encode 安全 |
Decoder 状态 |
流式增量读入 | 一次性读入整个 Reader;大型流建议分块传入或使用 bufio.Reader 包装 |
omitzero struct tag |
Go 1.24+ 支持 | 全版本支持(随标准库升级一起影响) |
最小化迁移风险: 可先用
json.SetOptions(json.Options{EscapeHTML: true, NumberMode: json.NumberFloat64Mode})使行为与encoding/json完全一致,确认测试通过后再逐步切换到性能优先模式。
yakjson 使用 atomic.Pointer[Config] 快照模式 — 并发安全、运行时可动态调整:
- 推荐阈值 (
DefaultMax*): 编译期常量,超过时SetLimits返回 warning - 安全限制 (
SetLimits): 自由设置,不做强制 clamp;零值字段 = 不修改 - 行为选项 (
SetOptions): 控制序列化/反序列化行为;零值 = 默认最优 - 原子快照 (
GetConfig): 获取当前配置副本(只读)
每次 Parse / Marshal / Unmarshal / AcquireWriter 在入口处执行一次 atomic.Load 获取 *Config 快照(~1ns),
整个操作使用同一快照,保证配置一致性且热路径零额外原子操作。运行时任何时刻均可安全调整配置,对进行中的操作无影响。
import json "github.com/uniyakcom/yakjson"
func init() {
// 超过 DefaultMax* 推荐阈值的字段会返回 warning 字符串
if warnings := json.SetLimits(json.Limits{
MaxDepth: 128, // 降低解析最大嵌套深度
MaxArrayLength: 10000, // 限制数组最大元素数
MaxMarshalDepth: 50, // 限制序列化递归深度
// 零值字段不修改(保持默认)
}); len(warnings) > 0 {
log.Printf("yakjson config warnings: %v", warnings)
}
}func init() {
json.SetOptions(json.Options{
EscapeHTML: true, // 转义 <, >, & 为 \uXXXX(防 XSS)
NaNInf: json.NaNInfError, // NaN/Inf 返回错误而非 null
NumberMode: json.NumberFloat64Mode, // 数字统一使用 float64(兼容 encoding/json)
StrictIntParse: true, // "1.9"→int 报错而非截断
StrictNumbers: true, // 拒绝前导零(01)和 float64 溢出(1e9999)
})
}cfg := json.GetConfig()
fmt.Printf("MaxDepth: %d, EscapeHTML: %v\n", cfg.MaxDepth, cfg.EscapeHTML)func TestSomething(t *testing.T) {
defer json.ResetDefaults()
json.SetLimits(json.Limits{MaxDepth: 5})
// ... 测试逻辑 ...
}| 函数/方法 | 说明 |
|---|---|
Get(json, path string) Res |
点路径懒查询(零分配) |
GetBytes(json []byte, path string) Res |
字节切片版本 |
Res.String() string |
字符串值(非字符串返回原始 JSON) |
Res.Int() int64 |
整数值 |
Res.Float64() float64 |
浮点值 |
Res.Bool() bool |
布尔值 |
Res.Exists() bool |
值是否存在 |
Res.Raw() string |
原始 JSON 切片 |
Res.Type() Type |
值类型 |
| 方法 | 说明 |
|---|---|
Parse(s string) (*Value, error) |
解析 JSON 字符串 |
ParseBytes(b []byte) (*Value, error) |
解析 JSON 字节切片 |
AcquireParser() *Parser |
从池获取(并发安全) |
ReleaseParser(p *Parser) |
归还到池 |
| 方法 | 说明 |
|---|---|
Type() Type |
值类型(Null/Bool/Number/String/Array/Object) |
Get(keys ...string) *Value |
嵌套路径访问 |
GetString(keys ...string) string |
获取字符串 |
GetStringBytes(keys ...string) []byte |
获取字符串字节切片(只读) |
GetInt(keys ...string) int |
获取整数 |
GetInt64(keys ...string) int64 |
获取 64 位整数 |
GetFloat64(keys ...string) float64 |
获取浮点数 |
GetBool(keys ...string) bool |
获取布尔值 |
IsNull() / IsObject() / IsArray() |
类型判断 |
Len() int |
数组/对象元素数量 |
ArrayEach(fn) |
遍历数组 |
ObjectEach(fn) |
遍历对象(保持字段顺序) |
Values() []*Value |
数组元素切片副本(TypeArray 专用) |
KVs() []KV |
对象键值对切片副本(TypeObject 专用) |
Raw() string |
原始数字/字符串字面量 |
| 方法 | 说明 |
|---|---|
AcquireWriter() *Writer |
从池获取 |
ReleaseWriter(w *Writer) |
归还到池 |
Object(fn func(w *Writer)) |
构建对象 |
Array(fn func(w *Writer)) |
构建数组 |
Field(key, value string) |
字符串字段 |
FieldInt / FieldInt64 / FieldUint64 |
整数字段 |
FieldFloat(key string, value float64) |
浮点数字段 |
FieldBool(key string, value bool) |
布尔字段 |
FieldNull(key string) |
null 字段 |
FieldBytes(key string, value []byte) |
字节切片字段 |
FieldObject / FieldArray |
嵌套对象/数组字段 |
FieldRaw(key string, rawJSON []byte) |
原始 JSON 字段 |
Item / ItemInt / ItemFloat / ItemBool / ItemNull |
数组元素 |
ItemObject / ItemArray |
嵌套数组元素 |
Bytes() []byte |
获取结果 |
String() string |
获取结果字符串 |
Len() int |
当前 buffer 长度 |
Reset() |
重置 buffer |
AppendTo(dst []byte) []byte |
追加到已有 buffer |
| 函数 | 说明 |
|---|---|
Marshal(v any) ([]byte, error) |
序列化(兼容 encoding/json) |
MarshalTo(dst []byte, v any) ([]byte, error) |
追加序列化到已有 buffer(零分配) |
Unmarshal(data []byte, v any) error |
反序列化(兼容 encoding/json) |
AcquireBuf() *[]byte |
从 pool 获取序列化缓冲区 |
ReleaseBuf(bp *[]byte) |
归还序列化缓冲区 |
| 函数 | 说明 |
|---|---|
Each(json, path string, fn func(key string, value Res) bool) |
流式遍历路径指向的数组/对象(path 为空遍历顶层) |
EachBytes(json []byte, path string, fn ...) |
字节切片版本 |
| 函数 | 说明 |
|---|---|
Set(json, path string, value any) (string, error) |
就地设置/新增 JSON 路径值 |
SetBytes(json []byte, path string, value any) ([]byte, error) |
字节切片版本 |
SetMany(json string, ops ...SetOp) (string, error) |
批量设置多个路径值 |
SetManyBytes(json []byte, ops ...SetOp) ([]byte, error) |
字节切片版本 |
SetOp{Path, Value} |
描述一次 Set 操作 |
| 函数/类型 | 说明 |
|---|---|
MarshalIndent(v, prefix, indent) ([]byte, error) |
格式化序列化(兼容 encoding/json) |
MarshalWrite(w io.Writer, v any) error |
流式序列化 |
UnmarshalRead(r io.Reader, v any) error |
流式反序列化 |
Valid(data []byte) bool |
JSON 合法性检查(兼容 encoding/json.Valid) |
Validate(data []byte) error |
JSON 合法性校验,返回含偏移量的描述性错误 |
ValidateString(s string) error |
字符串版 Validate |
Compact(dst *bytes.Buffer, src []byte) error |
移除多余空白 |
HTMLEscape(dst *bytes.Buffer, src []byte) |
HTML 字符转义 |
NewEncoder(w io.Writer) *Encoder |
创建流式编码器(支持 SetEscapeHTML/SetIndent/Encode) |
NewDecoder(r io.Reader) *Decoder |
创建流式解码器(支持 Decode) |
MarshalOptions{EscapeHTML, NaNInf} |
配置式 Marshal(方法: Marshal()/MarshalWrite()) |
UnmarshalOptions{NumberMode, StrictIntParse, StrictNumbers} |
配置式 Unmarshal(方法: Unmarshal()/UnmarshalRead()) |
type RawMessage []byte |
原始 JSON 片段(Marshal 直接嵌入,Unmarshal 捕获原始 JSON 字节,不做 base64) |
type KV struct{Key string; Value *Value} |
对象键值对(由 Value.KVs() 返回) |
type MismatchTypeError struct{From, To, Path string} |
Unmarshal 类型不匹配错误(宽松模式:跳过字段继续,返回最后一个错误) |
| 函数/类型 | 说明 |
|---|---|
SetLimits(l Limits) []string |
设置安全限制(零值字段不修改,超过推荐阈值返回 warning) |
SetOptions(o Options) |
设置行为选项(零值 = 默认最优) |
ResetDefaults() |
重置所有配置到默认值(测试用) |
GetConfig() Config |
返回当前配置副本(只读快照) |
Config |
统一运行时配置(安全限制 + 行为选项) |
Limits |
安全限制配置结构体 |
Options |
行为选项配置结构体 |
LimitKind |
限制类型枚举(LimitDepth/LimitMarshalDepth/LimitKeyLength/LimitStringLength/LimitArrayLength/LimitObjectKeys) |
NumberInt64First / NumberFloat64Mode |
数字反序列化模式 |
DefaultMax* 常量 |
推荐安全阈值(编译期常量) |
yakjson/
├── json.go 配置系统(atomic.Pointer[Config] 快照 + DefaultMax* 推荐阈值 + SetLimits/SetOptions/GetConfig/ResetDefaults + LimitError 结构化错误)
├── parser.go 索引模式解析引擎 + Parser / ParserPool
├── get.go Get/Each 懒查询 + 流式遍历 API + Res 类型 + skipVal/skipNested
├── set.go Set/SetBytes/SetMany JSON 就地修改 + SetOp 批量操作
├── value.go Value 类型 + 路径访问 + 遍历器
├── number.go 手写 parseInt / parseFloat / parseUint(零分配)+ strictIntParse
├── writer.go 流式 JSON 构建器 + WriterPool(支持 escapeHTML)
├── marshal.go Marshal 入口 + buffer pool + 核心递归序列化 + 浮点/字符串/map/slice/array 编码
├── struct_cache.go structFieldInfo 类型 + atomic COW 字段缓存 + 嵌入字段去重 + SWAR 预计算
├── marshal_struct.go struct → JSON 快速路径(unsafe 直接内存访问 + reflect 回退 + ,string tag + omitempty/omitzero)
├── unmarshal.go Unmarshal 入口 + 流式解析(streamUnmarshal* + unsafe 直写 + parseAnyDirect)
├── compat_v2.go encoding/json/v2 风格适配层(Encoder/Decoder/MarshalIndent/Valid/Validate/ValidateString/Compact/HTMLEscape)
├── marshal_plan.go 预编译 struct 编码计划(22 种编码器,NaN/Inf 安全)
├── scan_amd64.go/.s SSE2/AVX2 字符串扫描原语(amd64)
├── scan_arm64.go/.s ARM64 NEON 字符串扫描原语
├── scan_swar.go SWAR 8 字节并行扫描(通用平台 fallback)
├── scan_scalar.go 逐字节标量扫描(大端序 / 非 SWAR 平台 fallback)
├── json_test.go 核心功能测试 + 安全边界测试
├── parser_test.go 解析引擎测试 + 基准测试
├── get_test.go 懒查询 API 测试
├── set_test.go 就地修改 API 测试
├── value_test.go Value 类型访问与遍历测试
├── number_test.go 数字解析测试 (parseInt/parseFloat)
├── writer_test.go Writer 序列化 + escapeHTML 测试
├── compat_test.go Marshal/Unmarshal + 标准库兼容性矩阵
├── config_test.go 配置系统单元测试
├── fuzz_test.go 多个 Fuzz 目标 + 种子语料
├── each_setmany_v2_test.go Each/SetMany/v2 兼容 API 测试
├── rfc_test.go RFC 8259 合规套件(§3-§8 全覆盖 + StrictNumbers + MaxDepth + Validate API)
├── corpus_test.go 三层 Fuzz 种子管理(inlineSeeds + securityVectors + loadCorpusFiles)
├── testdata/corpus/ 真实世界 JSON 语料(github_api.json / unicode_strings.json / numbers_boundary.json)
├── testdata/fuzz/ Go 原生 Fuzz 种子语料库(JSONTestSuite 向量 + sonic 崩溃复现)
├── bench.sh 基准测试脚本(自动保存为 bench_{os}_{cores}c{threads}t.txt)
├── bench_guard.sh 本地性能回归守卫(功能已整合到 CI,本地参考用)
├── bench_*_baseline.txt 本地平台基线(bench_windows_baseline.txt 等)
├── pprof_archive.sh 性能剖析归档脚本
├── .github/
│ ├── pull_request_template.md PR 提交规范(Conventional Commits 类型参考表)
│ ├── RULESETS.md 分支/标签规则集说明
│ ├── SETUP.md 新仓库 GitHub 手动配置步骤
│ ├── WORKFLOW.md CI/CD 工作流说明
│ ├── rulesets/
│ │ ├── protect-branch-main.json main 分支保护规则(squash-only + 必要检查)
│ │ └── protect-semver-tags.json 语义化版本标签保护规则
│ └── workflows/
│ ├── format.yml lint 门禁(format + vet),push + PR
│ ├── test.yml 单元测试 + race 检测(ubuntu / windows 矩阵),push + PR
│ ├── bench.yml 4 平台并发基准测试;PR 对比;结果归档到 bench 分支
│ ├── fuzz.yml seed 回归(push + PR)+ nightly fuzz(schedule)
│ └── release.yml push to main 扫描 Conventional Commits,自动计算 semver,创建/更新草稿 Release
├── .githooks/pre-commit 提交前检查钩子
├── CHANGELOG.md 版本变更日志
├── LICENSE MIT 许可证
└── .gitignore Git 忽略规则
| 推荐阈值 | 默认值 | 可调 | 说明 |
|---|---|---|---|
DefaultMaxDepth |
512 | ✅ SetLimits |
防栈溢出 / 深嵌套 JSON 炸弹 |
DefaultMaxKeyLength |
64 KB | ✅ | 防巨型 key DoS |
DefaultMaxStringLength |
16 MB | ✅ | 防巨型字符串 OOM(Parser + Get + Unmarshal 三路径) |
DefaultMaxArrayLength |
1M 元素 | ✅ | 防巨型数组耗尽内存(Parse + Unmarshal 双路径) |
DefaultMaxObjectKeys |
64K 键 | ✅ | 防超多键导致 O(n²) 查找退化 |
DefaultMaxMarshalDepth |
1000 | ✅ | 防自引用指针链导致 Marshal 栈溢出 |
DefaultPoolBufMax |
1 MB | ✅ | sync.Pool 保留的最大缓冲区(运行时默认 64 KB) |
| 控制字符 | 拒绝 0x00-0x1F | — | RFC 8259 §7 合规 |
SetLimits不做强制 clamp — 值可自由设置,超过DefaultMax*推荐阈值时返回 warning 字符串。
所有安全限制超出时返回 *LimitError,可通过 errors.As 做程序化判别:
import "errors"
var le *json.LimitError
if errors.As(err, &le) {
switch le.Kind {
case json.LimitDepth:
log.Printf("嵌套过深: %d > %d", le.Actual, le.Limit)
case json.LimitArrayLength:
log.Printf("数组过长: %d > %d", le.Actual, le.Limit)
}
}支持的 LimitKind 枚举:
| Kind | 对应 Config 字段 |
|---|---|
LimitDepth |
MaxDepth |
LimitMarshalDepth |
MaxMarshalDepth |
LimitKeyLength |
MaxKeyLength |
LimitStringLength |
MaxStringLength |
LimitArrayLength |
MaxArrayLength |
LimitObjectKeys |
MaxObjectKeys |
| 选项 | 默认值 | 说明 |
|---|---|---|
EscapeHTML |
false |
true: 转义 <, >, & 为 \uXXXX(防 XSS) |
NaNInf |
NaNInfNull |
NaNInfError: NaN/Inf 返回错误 |
NumberMode |
NumberInt64First |
NumberFloat64Mode: 兼容 encoding/json 语义 |
StrictIntParse |
false |
true: "1.9"→int 报错而非截断 |
StrictNumbers |
false |
true: 拒绝前导零整数(01/001)和 float64 溢出数字(1e9999) |
go get github.com/uniyakcom/yakjson# 快速测试(短模式 + race 检测)
go test ./... -short -race -count=1
# 详细输出
go test ./... -v -short -count=1
# 运行特定文件的测试
go test -run TestParse ./...
go test -run TestSet ./...测试文件与源文件 1:1 对应:
| 源文件 | 测试文件 | 说明 |
|---|---|---|
parser.go |
parser_test.go |
解析引擎 + 基准测试 |
value.go |
value_test.go |
Value 类型访问 + 遍历 |
writer.go |
writer_test.go |
Writer 序列化 + escapeHTML |
get.go |
get_test.go |
懒查询 API |
set.go |
set_test.go |
就地修改 API |
number.go |
number_test.go |
数字解析 (parseInt/parseFloat) |
marshal.go / struct_cache.go / marshal_struct.go / unmarshal.go |
compat_test.go |
Marshal/Unmarshal + 标准库兼容性矩阵 |
compat_v2.go / set.go / get.go |
each_setmany_v2_test.go |
Each/SetMany/v2 兼容 API |
json.go |
json_test.go + config_test.go |
安全限制 + 配置系统 |
| (跨切面) | fuzz_test.go |
全量 Fuzz 目标 |
| (跨切面) | corpus_test.go |
三层 Fuzz 种子管理(inlineSeeds / securityVectors / loadCorpusFiles) |
| (RFC 8259) | rfc_test.go |
RFC §3-§8 合规套件(Values/Objects/Arrays/Numbers/Strings/Unicode + StrictNumbers) |
# 仅运行种子语料(快速回归检查)
go test -run='^$' -fuzz=FuzzDiffStdlib -fuzztime=1x ./...
# 持续 Fuzz(发现新 bug)
go test -run='^$' -fuzz=FuzzDiffStdlib -fuzztime=30s ./...Fuzz 目标覆盖(完整列表见 fuzz_test.go):
- 解析/序列化 round-trip、标准库差异比对、并发安全
- 控制字符注入、key 转义 round-trip、JSON 注入防护
- 整数溢出、浮点边界、Unicode 边界条件
- 深度限制、字符串/key/数组长度限制、并发配置切换
- 哈希碰撞 key 、畸形 JSON 全 API 安全
# 运行基准测试并保存结果 (bench_{os}_{cores}c{threads}t.txt)
./bench.sh
# 自定义参数: benchtime=5s, count=5
./bench.sh 5s 5本地基线文件按平台区分(bench_windows_baseline.txt / bench_linux_baseline.txt 等),供本地参考。CI 已只用实时对比(尗段下方)。
Push 到 main / master 或提交 PR 自动触发,工作流按职责独立拆分:
| 工作流 | 触发 | 说明 |
|---|---|---|
format.yml |
push + PR | gofmt 格式检查 + go vet 静态分析 |
test.yml |
push + PR | 4 平台(2×amd64 + 2×arm64)× Go 1.25 矩阵,带 race 检测 |
fuzz.yml |
push + PR + 每日定时 | seed 回归(push/PR)+ 全目标并行 nightly fuzz(5 min/目标,crash 自动归档) |
bench.yml |
push to main / PR | 4 平台并发基准测试;PR 时 benchstat 对比;结果归档到 bench 分支 |
release.yml |
push to main | 扫描自上次 tag 以来的 Conventional Commits;自动计算 semver 版本号;创建/更新草稿 Release(需手动审核后公开) |
基于 Conventional Commits 自动计算版本号,发布无需合并 PR:
| 提交前缀 | 版本变化 |
|---|---|
feat: |
minor +1 |
feat!: / BREAKING CHANGE: |
major +1 |
fix: / perf: / refactor: / docs: / test: / build: / chore: |
patch +1 |
流程:
- 向
mainSquash merge 遵循 Conventional Commits 的 PR release.yml自动扫描自上次 Tag 以来的提交,按类型分类生成 Changelog,计算新版本号- GitHub Releases 页面自动创建/更新草稿 Release(含中英文分类 Changelog)
- 在 GitHub Releases 页面审核内容,确认无误后点击 Publish release 并填写 Tag(如
v1.2.3)公开发布 go get github.com/uniyakcom/yakjson@vX.Y.Z即可更新
无任何外部依赖,仅使用 Go 标准库 + unsafe。