fastjson解析速度约为标准库的1/8~1/10,尤其适合高吞吐、固定schema、无需精细错误控制的场景;但不支持自定义unmarshaljson、类型校验弱、不兼容struct标签,需谨慎替换。

fastjson 解析比标准库快多少,值不值得换
在真实服务压测中,fastjson 对典型 API 请求体(1–5KB JSON)的解析耗时通常只有 encoding/json 的 1/8~1/10;对含大量嵌套或重复字段的 JSON(如日志事件、监控指标),优势更明显。它不依赖反射,也不生成结构体代码,开箱即用。但代价是:不支持自定义 UnmarshalJSON 方法、不校验字段类型兼容性(比如把 "123" 当 int 解析失败时静默转为 0)、且无法直接复用已有 struct 标签。如果你的场景是「高吞吐 + 固定 schema + 不需要精细错误控制」,换 fastjson 是性价比极高的选择。
如何用 fastjson 替换标准库,避免 panic
最常见错误是直接把 json.Unmarshal(data, &v) 换成 fastjson.Unmarshal(data, &v) ——fastjson 的 Unmarshal 只接受指针到 map[string]interface{} 或 []interface{},不支持任意 struct。正确做法分两类:
- 若只需读取几个字段(如 API 网关提取
user_id和action):用fastjson.GetBytes或fastjson.GetString直接查路径,零分配、无 panic - 若需完整解析进 struct:先用
fastjson.Parse(data)得到*fastjson.Value,再手动调.GetInt/.GetString等方法赋值——这一步必须自己做边界检查,否则遇到缺失字段或类型不符会返回零值而非 error
示例:
v, err := fastjson.Parse(data)
if err != nil {
return err
}
userID := v.GetInt64("user", "id") // 路径支持多级
name := v.GetStringBytes("user", "name") // 返回 []byte,避免 string 转换开销
为什么 fastjson.Get* 方法返回 []byte 而不是 string
因为 fastjson 内部对原始 JSON 字符串做的是零拷贝切片引用:GetStringBytes 返回的 []byte 直接指向输入 data 的某一段内存。只要 data 还活着,这个 slice 就有效;一旦 data 被 GC 或重用,slice 就可能读到脏数据或 panic。所以:
- 不要把
GetStringBytes结果长期保存(比如塞进 map 或 struct 字段) - 如果后续要多次使用,立刻
string(b)转成 string(触发一次拷贝),或append([]byte{}, b...)复制一份 - 在 HTTP handler 中直接传给下游函数?可以,因为
data通常来自http.Request.Body读取的临时 buffer,生命周期可控
fastjson 在流式解析和大文件场景下的坑
fastjson 不提供类似 json.Decoder 的流式接口,所有解析都基于完整字节切片。这意味着:
- 不能边读边解析超大 JSON(如 GB 级日志文件),必须一次性加载进内存——容易 OOM
- 对
io.Reader输入,得先io.ReadAll(r),再传给fastjson.Parse,中间多一次内存拷贝 - 若真要处理大 JSON,应切换回
json.Decoder+json.RawMessage组合,或用goccy/go-json的Decoder.DisallowUnknownFields()做严格流式解析
真正适合 fastjson 的,是单次请求中「小而密」的 JSON payload,比如 REST API body、RPC 参数、Redis 缓存 value。











