Skip to content

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.

License

Notifications You must be signed in to change notification settings

uniyakcom/yakjson

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

yakjson

Go Version Go Reference Go Report Card License: MIT Lint Test Fuzz

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 字节切片,全程零拷贝
  • 懒查询 APIGet(json, "user.name") 点路径查询,不构建 DOM
  • 池化复用 — Parser / Writer 通过 sync.Pool 复用,并发安全
  • 兼容标准库 — 完整支持 json.Marshaler / json.Unmarshaler 接口和 struct tag(含 omitemptyomitzero,string、嵌入字段提升)
  • 流式序列化 — Writer 直接向 []byte 追加,无中间 io.Writer
  • 安全校验 — 最大嵌套深度、最大键长、最大字符串长度、控制字符拒绝,运行时可动态调整
  • RFC 8259 解析合规 — 拒绝未转义控制字符(§7)、非法数字格式(§6);Validate 返回含偏移量的描述性错误;StrictNumbers 额外拒绝前导零和 float64 溢出(HTML 转义和键排序默认关闭,RFC 不强制,与 sonic 策略一致)
  • []byte base64 全路径 — Marshal/Unmarshal 均支持 []byte ↔ base64(兼容 encoding/json
  • RawMessage 原始嵌入RawMessage 字段序列化时直接嵌入原始 JSON 片段,Unmarshal 时捕获原始字节
  • MismatchTypeError 宽松解析 — 类型不匹配时跳过字段继续,完成后返回最后一个 *MismatchTypeError
  • Value.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

小 JSON(~130B,struct 绑定)

基准测试 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

大 JSON(~13KB,20 个 GitHub Repo 对象)

基准测试 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-baseline

CI 基准测试由 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

快速开始

懒查询 Get(推荐: 单次取值场景)

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"

并发安全解析(ParserPool)

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
})

Writer 序列化

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}}

Marshal / Unmarshal(兼容 encoding/json)

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)

Set(就地修改 JSON)

// 修改已有字段
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"]}

Each(流式遍历)

// 遍历数组元素(零 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
})

SetMany(批量修改)

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"}

v2 兼容 API(Encoder / Decoder / MarshalIndent)

// 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 保护并发 EncodeSetEscapeHTML/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})
    // ... 测试逻辑 ...
}

API 参考

Get(懒查询)

函数/方法 说明
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 值类型

Parser

方法 说明
Parse(s string) (*Value, error) 解析 JSON 字符串
ParseBytes(b []byte) (*Value, error) 解析 JSON 字节切片
AcquireParser() *Parser 从池获取(并发安全)
ReleaseParser(p *Parser) 归还到池

Value

方法 说明
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 原始数字/字符串字面量

Writer

方法 说明
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 / Unmarshal

函数 说明
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(流式遍历)

函数 说明
Each(json, path string, fn func(key string, value Res) bool) 流式遍历路径指向的数组/对象(path 为空遍历顶层)
EachBytes(json []byte, path string, fn ...) 字节切片版本

Set(就地修改)

函数 说明
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 操作

v2 兼容 API

函数/类型 说明
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)

Fuzz 测试

# 仅运行种子语料(快速回归检查)
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 已只用实时对比(尗段下方)。

发布

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

流程:

  1. main Squash merge 遵循 Conventional Commits 的 PR
  2. release.yml 自动扫描自上次 Tag 以来的提交,按类型分类生成 Changelog,计算新版本号
  3. GitHub Releases 页面自动创建/更新草稿 Release(含中英文分类 Changelog)
  4. 在 GitHub Releases 页面审核内容,确认无误后点击 Publish release 并填写 Tag(如 v1.2.3)公开发布
  5. go get github.com/uniyakcom/yakjson@vX.Y.Z 即可更新

零依赖

无任何外部依赖,仅使用 Go 标准库 + unsafe

发展历史

  • 该库原为 yak 网络框架的内置 JSON 包
  • 现已独立为 yakjson,成为更通用的 JSON 处理库

License

MIT

About

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.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published