golang高性能JSON解析与验证插件库fastjson的使用

Golang高性能JSON解析与验证插件库fastjson的使用

fastjson - Go语言的快速JSON解析器和验证器

Build Status GoDoc Go Report codecov

特性

  • 快速:通常比标准库encoding/json快15倍
  • 无需模式:可以解析任意JSON而无需模式、反射、结构体魔法或代码生成
  • 简单API:提供简单易用的API
  • 高效访问多个字段:比jsonparsergjson更高效,因为fastjson只解析输入JSON一次
  • 验证功能:可以验证解析的JSON
  • 提取和修改:可以快速提取原始JSON的一部分并进行修改
  • 非同构数组:可以解析包含不同类型值的数组
  • 保留顺序:保留对象项原始顺序

已知限制

  • 需要特别注意对象引用的释放
  • 不能从io.Reader解析JSON

使用示例

单行代码访问单个字段

s := []byte(`{"foo": [123, "bar"]}`)
fmt.Printf("foo.0=%d\n", fastjson.GetInt(s, "foo", "0"))

// 输出:
// foo.0=123

访问多个字段并处理错误

package main

import (
	"fmt"
	"github.com/valyala/fastjson"
	"log"
)

func main() {
	var p fastjson.Parser
	v, err := p.Parse(`{
		"str": "bar",
		"int": 123,
		"float": 1.23,
		"bool": true,
		"arr": [1, "foo", {}]
	}`)
	if err != nil {
		log.Fatal(err)
	}
	
	fmt.Printf("str=%s\n", v.GetStringBytes("str"))
	fmt.Printf("int=%d\n", v.GetInt("int"))
	fmt.Printf("float=%f\n", v.GetFloat64("float"))
	fmt.Printf("bool=%v\n", v.GetBool("bool"))
	fmt.Printf("arr.1=%s\n", v.GetStringBytes("arr", "1"))

	// 输出:
	// str=bar
	// int=123
	// float=1.230000
	// bool=true
	// arr.1=foo
}

性能优化建议

  1. 重用ParserScanner来解析多个JSON,减少内存分配开销
  2. 当需要从JSON获取多个字段时,优先使用Parser返回的Value上的Get*方法
  3. 对于具有共同前缀路径的字段,先调用Value.Get获取共同前缀,然后再调用Get*方法
  4. 使用range循环遍历Value.GetArray返回的数组,而不是为每个数组项调用Get*

基准测试

Go 1.12下的基准测试结果:

$ GOMAXPROCS=1 go test github.com/valyala/fastjson -bench='Parse$'
goos: linux
goarch: amd64
pkg: github.com/valyala/fastjson
BenchmarkParse/small/stdjson-map         	  200000	      7305 ns/op	  26.01 MB/s	     960 B/op	      51 allocs/op
BenchmarkParse/small/stdjson-struct      	  500000	      3431 ns/op	  55.37 MB/s	     224 B/op	       4 allocs/op
BenchmarkParse/small/stdjson-empty-struct         	  500000	      2273 ns/op	  83.58 MB/s	     168 B/op	       2 allocs/op
BenchmarkParse/small/fastjson                     	 5000000	       347 ns/op	 547.53 MB/s	       0 B/op	       0 allocs/op
BenchmarkParse/small/fastjson-get                 	 2000000	       620 ns/op	 306.39 MB/s	       0 B/op	       0 allocs/op

安全注意事项

  • fastjson在解析攻击者精心构造的输入字符串时不应崩溃或恐慌
  • fastjson解析inputJSON字符串最多需要sizeof(Value) * len(inputJSON)字节的内存

常见问题

Q: 为什么还要创建另一个JSON解析包?

A: 因为其他包要么需要通过结构体和代码生成来定义严格的JSON模式,要么在需要从解析的JSON中获取多个不相关字段时性能不佳。

Q: fastjson的主要用途是什么?

A: 用于RTB和其他JSON-RPC服务的高性能JSON解析。

Q: 为什么fastjson不提供快速的编组(序列化)?

A: 它确实提供了一些编组功能,但对于高性能JSON编组,建议使用quicktemplate。


更多关于golang高性能JSON解析与验证插件库fastjson的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang高性能JSON解析与验证插件库fastjson的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Go高性能JSON解析与验证:fastjson库使用指南

fastjson是一个高性能的Go语言JSON解析库,相比标准库的encoding/json,它在处理大量JSON数据时能提供更好的性能。下面我将详细介绍fastjson的使用方法。

安装fastjson

go get github.com/valyala/fastjson

基本使用方法

1. 解析JSON字符串

package main

import (
	"fmt"
	"github.com/valyala/fastjson"
)

func main() {
	var p fastjson.Parser
	jsonData := `{"name": "John", "age": 30, "skills": ["Go", "Python"]}`

	v, err := p.Parse(jsonData)
	if err != nil {
		panic(err)
	}

	fmt.Printf("Name: %s\n", v.GetStringBytes("name"))
	fmt.Printf("Age: %d\n", v.GetInt("age"))
	
	skills := v.GetArray("skills")
	for i, skill := range skills {
		fmt.Printf("Skill %d: %s\n", i+1, skill.String())
	}
}

2. 创建JSON对象

package main

import (
	"fmt"
	"github.com/valyala/fastjson"
)

func main() {
	var a fastjson.Arena
	obj := a.NewObject()
	
	obj.Set("name", a.NewString("Alice"))
	obj.Set("age", a.NewNumberInt(28))
	
	skills := a.NewArray()
	skills.SetArrayItem(0, a.NewString("Java"))
	skills.SetArrayItem(1, a.NewString("JavaScript"))
	obj.Set("skills", skills)

	fmt.Println(obj.String())
}

高级功能

1. 验证JSON结构

package main

import (
	"fmt"
	"github.com/valyala/fastjson"
)

func main() {
	schema := `{
		"type": "object",
		"properties": {
			"name": {"type": "string"},
			"age": {"type": "number"},
			"skills": {
				"type": "array",
				"items": {"type": "string"}
			}
		},
		"required": ["name", "age"]
	}`

	var p fastjson.Parser
	jsonData := `{"name": "Bob", "age": 35, "skills": ["C++", "Rust"]}`

	v, err := p.Parse(jsonData)
	if err != nil {
		panic(err)
	}

	// 验证JSON结构
	if err := fastjson.Validate(schema, v); err != nil {
		fmt.Printf("Validation error: %v\n", err)
	} else {
		fmt.Println("JSON is valid")
	}
}

2. 高性能解析大量JSON数据

package main

import (
	"fmt"
	"github.com/valyala/fastjson"
)

func main() {
	jsonData := `[
		{"id": 1, "value": "data1"},
		{"id": 2, "value": "data2"},
		{"id": 3, "value": "data3"}
	]`

	var p fastjson.Parser
	v, err := p.Parse(jsonData)
	if err != nil {
		panic(err)
	}

	items := v.GetArray()
	for _, item := range items {
		id := item.GetInt("id")
		value := item.GetStringBytes("value")
		fmt.Printf("ID: %d, Value: %s\n", id, value)
	}
}

性能优化技巧

  1. 复用Parser和Arena对象:避免频繁创建和销毁这些对象
  2. 使用GetStringBytes代替GetString:减少内存分配
  3. 直接访问字段:避免多次调用Get方法
package main

import (
	"fmt"
	"github.com/valyala/fastjson"
)

func main() {
	var p fastjson.Parser
	var a fastjson.Arena
	
	// 复用Parser解析多个JSON
	json1 := `{"key": "value1"}`
	json2 := `{"key": "value2"}`

	v1, _ := p.Parse(json1)
	v2, _ := p.Parse(json2)
	
	fmt.Println(v1.GetStringBytes("key"))
	fmt.Println(v2.GetStringBytes("key"))
	
	// 复用Arena创建多个JSON对象
	obj1 := a.NewObject()
	obj1.Set("id", a.NewNumberInt(1))
	
	obj2 := a.NewObject()
	obj2.Set("id", a.NewNumberInt(2))
	
	fmt.Println(obj1.String())
	fmt.Println(obj2.String())
}

与标准库对比

fastjson相比标准库encoding/json有以下优势:

  1. 更快的解析速度:特别是对于大型JSON数据
  2. 更低的内存使用:减少内存分配次数
  3. 流式处理能力:可以处理部分JSON数据

但需要注意:

  1. API与标准库不同,学习曲线稍陡
  2. 某些情况下错误信息不如标准库详细

总结

fastjson是处理高性能JSON需求的优秀选择,特别适合:

  • 需要处理大量JSON数据的场景
  • 对性能要求严格的应用程序
  • 需要验证JSON结构的场景

对于简单的JSON处理,标准库可能更合适,但在性能关键路径上,fastjson能带来显著的性能提升。

回到顶部