Skip to content

Processor 入门 ​

本指南帮助你理解何时以及如何使用 Processor,相比包级函数能带来什么优势。

包函数 vs Processor ​

CyberGo JSON 提供两种 API 风格:

维度包级函数Processor
典型调用json.GetString(data, "name")p.GetString(data, "name")
创建方式无需创建,直接调用p, err := json.New()
配置方式每次调用传入 cfg ...Config创建时统一配置,后续复用
缓存全局共享缓存独立缓存,可控可清理
资源管理自动(全局处理器)手动 Close()
钩子系统不支持支持 AddHook
预解析不支持支持 PreParse + GetFromParsed
路径预编译不支持支持 CompilePath + GetCompiled
适用场景简单操作、脚本、低频调用高频操作、自定义配置、服务端

快速判断

  • 用包函数:偶尔操作 JSON、不想管理生命周期、快速脚本
  • 用 Processor:需要自定义配置、高频查询同一数据、需要钩子/审计

什么时候用 Processor ​

场景 1:自定义配置 ​

包级函数使用默认配置。如果需要安全模式、自定义编码器或钩子,使用 Processor:

go
// 包函数 — 始终使用默认配置
val := json.GetString(data, "name")

// Processor — 可自定义配置
cfg := json.SecurityConfig() // 安全模式
p, err := json.New(cfg)
if err != nil {
    panic(err)
}
defer p.Close()

// 后续所有操作都使用安全配置
val, err := p.Get(data, "name")

场景 2:高频查询同一数据(PreParse 优化) ​

对同一 JSON 多次查询时,PreParse 只解析一次,后续查询复用解析结果:

go
p, err := json.New()
if err != nil {
    panic(err)
}
defer p.Close()

// 一次解析
parsed, err := p.PreParse(largeJSON)
if err != nil {
    panic(err)
}
defer parsed.Release() // 用完归还对象池

// 多次查询 — 复用解析结果,避免重复解析
name, _ := p.GetFromParsed(parsed, "user.name")
email, _ := p.GetFromParsed(parsed, "user.email")
tags, _ := p.GetFromParsed(parsed, "tags")

// 也可以直接取底层解析结果(map[string]any / []any)
data := parsed.Data()
_ = data

// 修改同样可基于预解析结果:SetFromParsed 返回新的 ParsedJSON,原对象不变
modified, err := p.SetFromParsed(parsed, "user.age", 31)
if err != nil {
    panic(err)
}
newAge, _ := p.GetFromParsed(modified, "user.age")

性能对比

  • 包函数 GetString:每次调用都会解析 JSON(有缓存但命中率取决于场景)
  • PreParse + GetFromParsed:解析一次,N 次查询只做导航,零重复解析

场景 3:同一路径高频查询(CompilePath 优化) ​

PreParse 优化的是「同一份 JSON 查询多次」;如果场景是「同一路径在大量不同 JSON 上反复执行」,则用 CompilePath 预编译路径——路径解析与校验只做一次,之后每次查询直接导航:

go
package main

import (
	"fmt"

	"github.com/cybergodev/json"
)

func main() {
	p, err := json.New()
	if err != nil {
		panic(err)
	}
	defer p.Close()

	// 路径只编译一次(解析 + 校验)
	compiled, err := p.CompilePath("user.name")
	if err != nil {
		panic(err)
	}
	defer compiled.Release() // 归还对象池

	// 热路径中反复查询:跳过路径解析,只做导航
	for _, data := range []string{
		`{"user":{"name":"Alice"}}`,
		`{"user":{"name":"Bob"}}`,
	} {
		val, err := p.GetCompiled(data, compiled)
		if err != nil {
			panic(err)
		}
		fmt.Println(val)
	}
	// 输出:
	// Alice
	// Bob
}

两种优化的分工

优化省去的开销适用场景
PreParse + GetFromParsed重复解析 JSON 文档同一份 JSON 查询多个不同路径
CompilePath + GetCompiled重复解析路径表达式同一路径作用于多份 JSON(热路径)

两者是独立的优化维度,按瓶颈选择。注意 GetCompiled 目前只有查询变体,Set/Delete 暂不支持预编译路径;预解析侧的修改则可走 SetFromParsed。

场景 4:钩子与审计 ​

需要日志记录、性能监控或输入验证时,Processor 支持钩子系统:

go
p, err := json.New()
if err != nil {
    panic(err)
}
defer p.Close()

// 添加日志钩子
p.AddHook(json.LoggingHook(slog.Default()))
// 添加计时钩子
p.AddHook(json.TimingHook(&metricsRecorder))

// 所有操作自动触发钩子
result, err := p.Set(data, "user.name", "Alice")

详见 Hook 钩子系统。

场景 5:多 goroutine 共享 Processor ​

Processor 是并发安全的——正确姿势是创建一次、全组共享、最后 Close 一次,而不是每个请求各建一个(后者徒增创建开销,还会放大资源管理成本):

go
package main

import (
	"fmt"
	"sync"

	"github.com/cybergodev/json"
)

func main() {
	p, err := json.New()
	if err != nil {
		panic(err)
	}
	defer p.Close() // 等全部 goroutine 结束后才执行

	data := `{"user":{"name":"Alice","age":30}}`

	var wg sync.WaitGroup
	for i := 1; i <= 8; i++ {
		wg.Add(1)
		go func(i int) {
			defer wg.Done()
			name := p.GetString(data, "user.name")
			age := p.GetInt(data, "user.age")
			fmt.Printf("goroutine %d: %s (%d)\n", i, name, age)
		}(i)
	}
	wg.Wait()

	stats := p.GetStats()
	fmt.Println("累计操作数:", stats.OperationCount)
}

// 输出(goroutine 顺序不确定):
// goroutine 5: Alice (30)
// goroutine 2: Alice (30)
// ...
// 累计操作数: 16

MaxConcurrency 是软限制

默认 MaxConcurrency = 50:在途操作超过该值时,新操作立即失败并返回 ErrConcurrencyLimit(不排队等待)。高并发服务按需调大该值,或在调用方做限流与重试。

场景 6:全局统一配置 ​

包级函数背后是一个全局处理器。当希望整个应用——包括那些无法改造传参的旧代码——统一走同一份配置时,用 SetGlobalProcessor 一次替换,所有 json.Get/json.Marshal 等包级调用立即生效。完整示例与注意事项见下文全局处理器小节。

生命周期管理 ​

Processor 持有资源(缓存、goroutine),使用后必须关闭:

go
p, err := json.New()
if err != nil {
    panic(err)
}
defer p.Close() // 确保资源释放

// 使用 Processor...
result, err := p.GetString(data, "name")

忘记 Close 的后果

  • 缓存内存不会释放
  • 后台 goroutine 泄漏
  • 高并发场景下可能导致资源耗尽

检查状态 ​

go
if p.IsClosed() {
    // Processor 已关闭,不可再使用
}

IsClosed 在两种状态下都返回 true:已彻底关闭,或正在关闭(排空等待期)/关闭超时。这两种状态下新的操作都会被拒绝并返回错误,因此把它当作「还能不能用」的唯一判断即可。

监控与诊断 ​

Processor 内置运行统计与健康检查,适合接入服务监控:

go
package main

import (
	"fmt"

	"github.com/cybergodev/json"
)

func main() {
	p, err := json.New()
	if err != nil {
		panic(err)
	}
	defer p.Close()

	_, _ = p.Get(`{"user":{"name":"Alice"}}`, "user.name")

	// 运行统计:操作数、错误数、缓存命中率与内存占用
	stats := p.GetStats()
	fmt.Printf("操作数=%d 错误数=%d 命中率=%.2f 缓存条目=%d\n",
		stats.OperationCount, stats.ErrorCount, stats.HitRatio, stats.CacheSize)

	// 健康检查:缓存、内存等逐项检查结果
	health := p.GetHealthStatus()
	fmt.Println("健康:", health.Healthy)
	for name, check := range health.Checks {
		fmt.Printf("  %s: %s\n", name, check.Message)
	}

	// 读取当前配置(返回副本,修改不会影响 Processor)
	cfg := p.GetConfig()
	fmt.Println("缓存启用:", cfg.EnableCache)
}

包级版本

全局处理器同样有包级监控入口:json.GetStats() 与 json.GetHealthStatus(),适合在不持有 Processor 引用的代码里做全局诊断。缓存统计与 ClearCache/WarmupCache 的完整用法见 高级缓存策略。

全局处理器 ​

包级函数(Get、Set、Marshal 等)内部使用全局处理器。你也可以替换它:

go
// 创建自定义配置的处理器
cfg := json.SecurityConfig()
p, err := json.New(cfg)
if err != nil {
    panic(err)
}

// 设置为全局处理器
json.SetGlobalProcessor(p)

// 现在所有包级函数都使用安全配置
val := json.GetString(data, "name")

// 应用退出时清理
defer json.ShutdownGlobalProcessor()

行为细节:

  • SetGlobalProcessor 线程安全,传 nil 是 no-op;替换时会自动关闭旧处理器
  • ShutdownGlobalProcessor 是完整的退出清理:除关闭全局处理器外,还会关闭「按配置缓存」的处理器并清空全局路径/编码缓存;此后再调用包级函数会自动创建新的默认处理器
  • 传入 cfg 的包级函数(如 json.Get(data, path, json.SecurityConfig()))走的是按配置缓存的处理器,不经全局处理器——两种机制并行、互不影响

适用场景

  • 全局统一安全策略
  • 自定义编码器全局生效
  • 需要替换默认配置而无需到处传 Config

选择决策树 ​

需要操作 JSON?
├── 偶尔使用、脚本工具
│   └── → 用包函数 json.GetString / json.Set / json.Marshal
├── 偶尔使用,但需要安全/编码配置
│   └── → 包函数 + 尾参 cfg:json.Get(data, path, json.SecurityConfig())
├── 高频使用,或需要钩子等处理器能力
│   └── → 用 Processor json.New(cfg)
├── 对同一 JSON 多次查询
│   └── → 用 Processor + PreParse
├── 同一路径作用于大量 JSON(热路径)
│   └── → 用 Processor + CompilePath
├── 多个 goroutine 并发处理
│   └── → 共享一个 Processor(并发安全),不要每请求新建
├── 需要审计/监控/日志
│   └── → 用 Processor + AddHook
├── 需要运行时指标/健康检查
│   └── → 用 GetStats / GetHealthStatus(Processor 方法与包级函数均可)
└── 全局统一配置
    └── → 用 SetGlobalProcessor

下一步 ​