golang高性能无结构JSON解析与转换插件库ujson的使用

Golang高性能无结构JSON解析与转换插件库ujson的使用

ujson是一个快速、极简的JSON解析器和转换器,可以直接处理无结构的JSON数据。它通过解析输入并在遇到每个项时调用给定的回调函数来工作。

动机

有时我们只想对JSON文档进行最小更改,或者在不完全解组的情况下进行一些通用转换。例如,从响应JSON中移除黑名单字段。为什么要花费所有成本将JSON解组到map[string]interface{}中,然后再立即重新编组呢?

使用示例

1. 按顺序打印所有键和值

input := []byte(`{  
    "id": 12345,    
    "name": "foo",  
    "numbers": ["one", "two"],  
    "tags": {"color": "red", "priority": "high"},   
    "active": true  
}`)

ujson.Walk(input, func(level int, key, value []byte) bool {
    fmt.Printf("%2v% 12s : %s\n", level, key, value)
    return true
})

输出结果:

level key value
0 {
1 “id” 12345
1 “name” “foo”
1 “numbers” [
2 “one”
2 “two”
1 ]
1 “tags” {
2 “color” “red”
2 “priority” “high”
1 }
1 “active” true
0 }

2. 重新格式化输入

input := []byte(`{"id":12345,"name":"foo","numbers":["one","two"],"tags":{"color":"red","priority":"high"},"active":true}`)

b := make([]byte, 0, 1024)
err := ujson.Walk(input, func(level int, key, value []byte) bool {
    if len(b) != 0 && ujson.ShouldAddComma(value, b[len(b)-1]) {
        b = append(b, ',')
    }
    b = append(b, '\n')
    for i := 0; i < level; i++ {
        b = append(b, '\t')
    }
    if len(key) > 0 {
        b = append(b, key...)
        b = append(b, ':')
    }
    b = append(b, value...)
    return true
})
if err != nil {
    panic(err)
}
fmt.Printf("%s\n", b)

输出结果:

{
    "id":12345,
    "name":"foo",
    "numbers":[
        "one",
        "two"
    ],
    "tags":{
        "color":"red",
        "priority":"high"
    },
    "active":true
}

3. 移除黑名单字段

input := []byte(`{
    "id": 12345,
    "name": "foo",
    "numbers": ["one", "two"],
    "tags": {"color": "red", "priority": "high"},
    "active": true
}`)

blacklistFields := [][]byte{
    []byte(`"numbers"`), // 注意引号
    []byte(`"active"`),
}
b := make([]byte, 0, 1024)
err := ujson.Walk(input, func(_ int, key, value []byte) bool {
    for _, blacklist := range blacklistFields {
        if bytes.Equal(key, blacklist) {
            // 从输出中移除键和值
            return false
        }
    }

    // 写入输出
    if len(b) != 0 && ujson.ShouldAddComma(value, b[len(b)-1]) {
        b = append(b, ',')
    }
    if len(key) > 0 {
        b = append(b, key...)
        b = append(b, ':')
    }
    b = append(b, value...)
    return true
})
if err != nil {
    panic(err)
}
fmt.Printf("%s\n", b)

输出结果:

{"id":12345,"name":"foo","tags":{"color":"red","priority":"high"}}

4. 将int64包装为字符串

input := []byte(`{"order_id": 12345678901234, "number": 12, "item_id": 12345678905678, "counting": [1,"2",3]}`)

suffix := []byte(`_id"`) // 注意结尾的引号"
b := make([]byte, 0, 256)
err := ujson.Walk(input, func(_ int, key, value []byte) bool {
    // 测试键是否以_id"结尾且值为int64数字
    shouldWrap := bytes.HasSuffix(key, suffix) && value[0] > '0' && value[0] <= '9'

    // 转换输入,将值用双引号包裹
    if len(b) != 0 && ujson.ShouldAddComma(value, b[len(b)-1]) {
        b = append(b, ',')
    }
    if len(key) > 0 {
        b = append(b, key...)
        b = append(b, ':')
    }
    if shouldWrap {
        b = append(b, '"')
    }
    b = append(b, value...)
    if shouldWrap {
        b = append(b, '"')
    }
    return true
})
if err != nil {
    panic(err)
}
fmt.Printf("%s\n", b)

输出结果:

{"order_id":"12345678901234","number":12,"item_id":"12345678905678","counting":[1,"2",3]}

注意事项

重要提示:对于无效的JSON,行为是未定义的。仅在受信任的输入上使用。对于不受信任的输入,你可能希望首先通过json.Valid()运行它。

替代方案

👉 查看新的库iter.json,它使用Go1.23迭代器语法来迭代无结构JSON 👈

package main

import "ezpkg.io/iter.json"

func main() {
    data := `{"id": 12345, "name": "foo", "numbers": ["one", "two"]}`
    // 👇 使用Go1.23迭代器语法迭代JSON
    for item, err := range iterjson.Parse([]byte(data)) {
        if err != nil { panic(err) }
        println(item.GetPathString(), item.Key, item.Token, item.Level)
    }
}

许可证

MIT


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

1 回复

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


ujson:Go语言高性能无结构JSON解析与转换库

ujson是一个高性能的Go语言JSON处理库,特别适合处理无固定结构的JSON数据。相比标准库的encoding/json,ujson在某些场景下能提供更快的解析速度和更低的内存消耗。

主要特性

  1. 无结构解析:不需要预先定义结构体即可解析JSON
  2. 高性能:比标准库更快,特别是在大数据量场景
  3. 低内存消耗:采用更高效的内存管理方式
  4. 链式操作:支持方便的链式调用方法
  5. 路径查询:支持类似JSONPath的查询方式

安装

go get github.com/oliveagle/json

基本用法

1. 解析JSON

package main

import (
	"fmt"
	"github.com/oliveagle/json"
)

func main() {
	// 示例JSON数据
	data := `{
		"name": "John",
		"age": 30,
		"cars": ["Ford", "BMW", "Fiat"],
		"address": {
			"street": "123 Main St",
			"city": "New York"
		}
	}`

	// 解析JSON
	obj, err := ujson.NewFromString(data)
	if err != nil {
		panic(err)
	}

	// 获取字段值
	name := obj.Get("name").String()
	age := obj.Get("age").Int()
	firstCar := obj.Get("cars").GetIndex(0).String()

	fmt.Printf("Name: %s, Age: %d, First Car: %s\n", name, age, firstCar)
}

2. 创建和修改JSON

func createAndModify() {
	// 创建新对象
	obj := ujson.NewObject()

	// 添加字段
	obj.Set("name", "Alice")
	obj.Set("age", 25)
	obj.Set("is_student", true)

	// 添加数组
	cars := ujson.NewArray()
	cars.Append("Toyota")
	cars.Append("Honda")
	obj.Set("cars", cars)

	// 添加嵌套对象
	address := ujson.NewObject()
	address.Set("street", "456 Oak Ave")
	address.Set("city", "Los Angeles")
	obj.Set("address", address)

	// 转换为JSON字符串
	jsonStr := obj.String()
	fmt.Println(jsonStr)
}

3. 路径查询

func pathQuery() {
	data := `{
		"store": {
			"book": [
				{
					"title": "The Go Programming Language",
					"price": 49.99
				},
				{
					"title": "Effective Go",
					"price": 29.99
				}
			],
			"bicycle": {
				"color": "red",
				"price": 199.99
			}
		}
	}`

	obj, _ := ujson.NewFromString(data)

	// 获取第一本书的标题
	title := obj.Get("store").Get("book").GetIndex(0).Get("title").String()
	fmt.Println("First book title:", title)

	// 获取自行车价格
	bikePrice := obj.Get("store").Get("bicycle").Get("price").Float64()
	fmt.Println("Bicycle price:", bikePrice)
}

性能对比

ujson在以下场景表现优异:

  1. 大JSON文件解析:比标准库快2-5倍
  2. 无结构数据访问:省去了反射开销
  3. 频繁修改操作:内部采用更高效的数据结构

注意事项

  1. ujson不适合需要严格类型检查的场景
  2. 对于固定结构的数据,标准库可能更合适
  3. 错误处理需要格外注意,链式调用中任何一步出错都会导致后续调用失败

高级用法

批量操作

func batchOperations() {
	data := `{"users": [{"id": 1, "name": "John"}, {"id": 2, "name": "Jane"}]}`
	obj, _ := ujson.NewFromString(data)

	// 批量修改用户名
	users := obj.Get("users")
	for i := 0; i < users.Len(); i++ {
		user := users.GetIndex(i)
		currentName := user.Get("name").String()
		user.Set("name", currentName + "_updated")
	}

	fmt.Println(obj.String())
}

自定义解析

func customParsing() {
	data := `{"timestamp": 1633036800}`
	obj, _ := ujson.NewFromString(data)

	// 将时间戳转换为time.Time
	timestamp := obj.Get("timestamp").Int64()
	tm := time.Unix(timestamp, 0)
	fmt.Println("Time:", tm.Format(time.RFC3339))
}

结论

ujson是处理无结构或半结构化JSON数据的优秀选择,特别适合以下场景:

  • 需要快速解析大型JSON
  • JSON结构不固定或经常变化
  • 需要灵活查询和修改JSON数据
  • 对性能有较高要求

对于有固定结构的JSON数据,标准库encoding/json可能仍然是更好的选择,因为它提供了更好的类型安全和更清晰的代码结构。

回到顶部