golang支持JSONPath的抽象JSON处理插件库ajson的使用

Golang 支持 JSONPath 的抽象 JSON 处理插件库 ajson 的使用

Abstract JSON (ajson) 是一个小型 Golang 包,提供了对 JSON 的解析器,并支持 JSONPath,适用于不确定 JSON 结构的情况。

基本功能

  • Unmarshal 方法会扫描整个字节切片来创建 JSON 结构的根节点
  • Marshal 方法会将当前 Node 对象序列化为 JSON 结构
  • 每个 Node 都有自己的类型和计算值(按需计算)
  • JSONPath 方法会根据 JSONPath 请求返回当前 JSON 数据中找到的元素切片

安装

import "github.com/spyzhov/ajson"

基本使用示例

package main

import (
    "fmt"
    "github.com/spyzhov/ajson"
)

func main() {
    json := []byte(`...`) // 你的JSON数据

    // 解析JSON
    root, _ := ajson.Unmarshal(json)
    
    // 使用JSONPath查找所有price节点
    nodes, _ := root.JSONPath("$..price")
    
    // 修改每个price节点的值并添加currency字段
    for _, node := range nodes {
        node.SetNumeric(node.MustNumeric() * 1.25)
        node.Parent().AppendObject("currency", ajson.StringNode("", "EUR"))
    }
    
    // 将修改后的JSON序列化
    result, _ := ajson.Marshal(root)

    fmt.Printf("%s", result)
}

JSONPath 支持

ajson 支持 JSONPath 选择器,语法参考 http://goessner.net/articles/JsonPath/

JSONPath 语法元素

JSONPath 描述
$ 根对象/元素
@ 当前对象/元素
.[] 子操作符
.. 递归下降(从 E4X 借鉴)
* 通配符(所有对象/元素不论名称)
[] 下标操作符
[,] 联合操作符(XPath 中用于合并节点集)
[start:end:step] 数组切片操作符(从 ES4 借鉴)
?() 应用过滤(脚本)表达式
() 脚本表达式,使用底层脚本引擎

高级功能示例

1. 使用 true 在路径中

package main

import (
    "fmt"
    "github.com/spyzhov/ajson"
)

func main() {
    json := []byte(`{"foo": [true, null, false, 1, "bar", true, 1e3], "bar": [true, "baz", false]}`)
    
    // 查找所有值为true的节点
    result, _ := ajson.JSONPath(json, `$..[?(@ == true)]`)
    
    fmt.Printf("Count of `true` values: %d", len(result))
    // 输出: Count of `true` values: 3
}

2. 使用 avg 函数计算数组平均值

package main

import (
    "fmt"
    "github.com/spyzhov/ajson"
)

func main() {
    json := []byte(`{"prices": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}`)
    
    // 解析JSON
    root, err := ajson.Unmarshal(json)
    if err != nil {
        panic(err)
    }
    
    // 使用avg函数计算平均值
    result, err := ajson.Eval(root, `avg($.prices)`)
    if err != nil {
        panic(err)
    }
    
    fmt.Printf("Avg price: %0.1f", result.MustNumeric())
    // 输出: Avg price: 5.5
}

3. 使用正则表达式匹配

package main

import (
    "fmt"
    "github.com/spyzhov/ajson"
)

func main() {
    json := []byte(`[{"name":"Foo","mail":"foo@example.com"},{"name":"bar","mail":"bar@example.org"}]`)
    
    // 使用正则匹配邮件地址
    result, err := ajson.JSONPath(json, `$.[?(@.mail =~ '.+@example\\.com')]`)
    if err != nil {
        panic(err)
    }
    
    fmt.Printf("JSON: %s", result[0].Source())
    // 输出: JSON: {"name":"Foo","mail":"foo@example.com"}
}

复杂JSON处理示例

计算平均价格

{
  "store": {
    "book": [
      {
        "category": "reference",
        "author": "Nigel Rees",
        "title": "Sayings of the Century",
        "price": 8.95
      },
      {
        "category": "fiction",
        "author": "Evelyn Waugh",
        "title": "Sword of Honour",
        "price": 12.99
      },
      {
        "category": "fiction",
        "author": "Herman Melville",
        "title": "Moby Dick",
        "isbn": "0-553-21311-3",
        "price": 8.99
      },
      {
        "category": "fiction",
        "author": "J. R. R. Tolkien",
        "title": "The Lord of the Rings",
        "isbn": "0-395-19395-8",
        "price": 22.99
      }
    ],
    "bicycle": {
      "color": "red",
      "price": 19.95
    },
    "tools": null
  }
}

使用 Unmarshal 处理

package main

import (
    "fmt"
    "github.com/spyzhov/ajson"
)

func main() {
    data := []byte(`{"store": {"book": [
{"category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95}, 
{"category": "fiction", "author": "Evelyn Waugh", "title": "Sword of Honour", "price": 12.99}, 
{"category": "fiction", "author": "Herman Melville", "title": "Moby Dick", "isbn": "0-553-21311-3", "price": 8.99}, 
{"category": "fiction", "author": "J. R. R. Tolkien", "title": "The Lord of the Rings", "isbn": "0-395-19395-8", "price": 22.99}], 
"bicycle": {"color": "red", "price": 19.95}, "tools": null}}`)

    root, err := ajson.Unmarshal(data)
    if err != nil {
        panic(err)
    }

    store := root.MustKey("store").MustObject()

    var prices float64
    size := 0
    for _, objects := range store {
        if objects.IsArray() && objects.Size() > 0 {
            size += objects.Size()
            for _, object := range objects.MustArray() {
                prices += object.MustKey("price").MustNumeric()
            }
        } else if objects.IsObject() && objects.HasKey("price") {
            size++
            prices += objects.MustKey("price").MustNumeric()
        }
    }

    if size > 0 {
        fmt.Println("AVG price:", prices/float64(size))
    } else {
        fmt.Println("AVG price:", 0)
    }
}

使用 JSONPath 处理

package main

import (
    "fmt"
    "github.com/spyzhov/ajson"
)

func main() {
    data := []byte(`{"store": {"book": [
{"category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95}, 
{"category": "fiction", "author": "Evelyn Waugh", "title": "Sword of Honour", "price": 12.99}, 
{"category": "fiction", "author": "Herman Melville", "title": "Moby Dick", "isbn": "0-553-21311-3", "price": 8.99}, 
{"category": "fiction", "author": "J. R. R. Tolkien", "title": "The Lord of the Rings", "isbn": "0-395-19395-8", "price": 22.99}], 
"bicycle": {"color": "red", "price": 19.95}, "tools": null}}`)

    // 使用JSONPath查找所有price节点
    nodes, err := ajson.JSONPath(data, "$..price")
    if err != nil {
        panic(err)
    }

    var prices float64
    size := len(nodes)
    for _, node := range nodes {
        prices += node.MustNumeric()
    }

    if size > 0 {
        fmt.Println("AVG price:", prices/float64(size))
    } else {
        fmt.Println("AVG price:", 0)
    }
}

使用 Eval 计算平均值

package main

import (
    "fmt"
    "github.com/spyzhov/ajson"
)

func main() {
    json := []byte(`{"store": {"book": [
{"category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95}, 
{"category": "fiction", "author": "Evelyn Waugh", "title": "Sword of Honour", "price": 12.99}, 
{"category": "fiction", "author": "Herman Melville", "title": "Moby Dick", "isbn": "0-553-21311-3", "price": 8.99}, 
{"category": "fiction", "author": "J. R. R. Tolkien", "title": "The Lord of the Rings", "isbn": "0-395-19395-8", "price": 22.99}], 
"bicycle": {"color": "red", "price": 19.95}, "tools": null}}`)
    
    root, err := ajson.Unmarshal(json)
    if err != nil {
        panic(err)
    }
    
    // 使用eval计算平均值
    result, err := ajson.Eval(root, "avg($..price)")
    if err != nil {
        panic(err)
    }
    
    fmt.Println("AVG price:", result.MustNumeric())
}

修改并重新序列化JSON

package main

import (
    "fmt"
    "github.com/spyzhov/ajson"
)

func main() {
    json := []byte(`{"store": {"book": [
{"category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95}, 
{"category": "fiction", "author": "Evelyn Waugh", "title": "Sword of Honour", "price": 12.99}, 
{"category": "fiction", "author": "Herman Melville", "title": "Moby Dick", "isbn": "0-553-21311-3", "price": 8.99}, 
{"category": "fiction", "author": "J. R. R. Tolkien", "title": "The Lord of the Rings", "isbn": "0-395-19395-8", "price": 22.99}], 
"bicycle": {"color": "red", "price": 19.95}, "tools": null}}`)
    
    root := ajson.Must(ajson.Unmarshal(json))
    result := ajson.Must(ajson.Eval(root, "avg($..price)"))
    
    // 添加平均价格到JSON
    err := root.AppendObject("price(avg)", result)
    if err != nil {
        panic(err)
    }
    
    // 序列化修改后的JSON
    marshalled, err := ajson.Marshal(root)
    if err != nil {
        panic(err)
    }
    
    fmt.Printf("%s", marshalled)
}

命令行工具

你可以从发布页面下载 ajson CLI,或者从源代码安装:

go get github.com/spyzhov/ajson/cmd/ajson@v0.9.6

使用示例:

ajson "avg($..registered.age)" "https://randomuser.me/api/?results=5000"
ajson "$.results.*.name" "https://randomuser.me/api/?results=10"
curl -s "https://randomuser.me/api/?results=10" | ajson "$..coordinates"
ajson "$" example.json
echo "3" | ajson "2 * pi * $"
docker logs image-name -f | ajson -qm 'root($[?(@=="ERROR" && key(@)=="severity")])'

性能

ajson 的性能与 encoding/json 包相当。基准测试结果示例:

$ go test -bench=. -cpu=1 -benchmem
goos: linux
goarch: amd64
pkg: github.com/spyzhov/ajson
BenchmarkUnmarshal_AJSON          121656             10060 ns/op            5712 B/op        118 allocs/op
BenchmarkUnmarshal_JSON           102674             11381 ns/op             960 B/op         32 allocs/op
BenchmarkJSONPath_all_prices       63314             16385 ns/op            7496 B/op        178 allocs/op

许可证

MIT 许可证。详见 LICENSE 文件。


更多关于golang支持JSONPath的抽象JSON处理插件库ajson的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang支持JSONPath的抽象JSON处理插件库ajson的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


使用 ajson 库处理 JSONPath 查询

ajson 是一个 Go 语言实现的 JSONPath 处理库,它提供了类似 JavaScript 中操作 JSON 的灵活方式。下面我将介绍如何使用 ajson 库进行 JSONPath 查询和处理。

安装 ajson

go get github.com/spyzhov/ajson

基本用法

1. 解析 JSON 数据

package main

import (
	"fmt"
	"github.com/spyzhov/ajson"
)

func main() {
	// 原始 JSON 数据
	jsonData := `{
		"store": {
			"book": [
				{
					"category": "reference",
					"author": "Nigel Rees",
					"title": "Sayings of the Century",
					"price": 8.95
				},
				{
					"category": "fiction",
					"author": "Evelyn Waugh",
					"title": "Sword of Honour",
					"price": 12.99
				}
			],
			"bicycle": {
				"color": "red",
				"price": 19.95
			}
		}
	}`

	// 解析 JSON
	root, err := ajson.Unmarshal([]byte(jsonData))
	if err != nil {
		panic(err)
	}
}

2. 使用 JSONPath 查询

// 查询所有书籍的作者
authors, err := root.JSONPath("$.store.book[*].author")
if err != nil {
	panic(err)
}

for _, author := range authors {
	fmt.Println("Author:", author.String())
}

// 查询价格低于10的书籍
cheapBooks, err := root.JSONPath("$.store.book[?(@.price < 10)]")
if err != nil {
	panic(err)
}

for _, book := range cheapBooks {
	title, _ := book.GetKey("title").String()
	price, _ := book.GetKey("price").Numeric()
	fmt.Printf("Cheap book: %s (%.2f)\n", title, price)
}

// 查询自行车颜色
color, err := root.JSONPath("$.store.bicycle.color")
if err != nil {
	panic(err)
}
fmt.Println("Bicycle color:", color[0].String())

3. 修改 JSON 数据

// 修改自行车价格
bicyclePrice, err := root.JSONPath("$.store.bicycle.price")
if err != nil {
	panic(err)
}

err = bicyclePrice[0].SetNumeric(15.99)
if err != nil {
	panic(err)
}

// 添加新书
books, err := root.JSONPath("$.store.book")
if err != nil {
	panic(err)
}

newBook := map[string]interface{}{
	"category": "fantasy",
	"author":   "J.R.R. Tolkien",
	"title":    "The Lord of the Rings",
	"price":    22.99,
}

err = books[0].AppendObject(newBook)
if err != nil {
	panic(err)
}

// 获取修改后的 JSON
modifiedJSON, err := ajson.Marshal(root)
if err != nil {
	panic(err)
}

fmt.Println(string(modifiedJSON))

高级功能

1. 使用过滤器

// 查找特定类别的书籍
fantasyBooks, err := root.JSONPath("$.store.book[?(@.category == 'fantasy')]")
if err != nil {
	panic(err)
}

for _, book := range fantasyBooks {
	title, _ := book.GetKey("title").String()
	fmt.Println("Fantasy book:", title)
}

2. 处理嵌套结构

// 获取所有价格
allPrices, err := root.JSONPath("$..price")
if err != nil {
	panic(err)
}

for _, price := range allPrices {
	p, _ := price.Numeric()
	fmt.Println("Price:", p)
}

3. 动态构建 JSON

// 创建新的 JSON 结构
newRoot := ajson.New()
storeNode := ajson.NewObject()
bookArray := ajson.NewArray()

book1 := ajson.NewObject()
book1.MustSetKey("title", ajson.StringNode("", "New Book 1"))
book1.MustSetKey("price", ajson.NumericNode("", 9.99))

book2 := ajson.NewObject()
book2.MustSetKey("title", ajson.StringNode("", "New Book 2"))
book2.MustSetKey("price", ajson.NumericNode("", 14.99))

bookArray.MustAppend(book1)
bookArray.MustAppend(book2)
storeNode.MustSetKey("book", bookArray)
newRoot.MustSetKey("store", storeNode)

newJSON, err := ajson.Marshal(newRoot)
if err != nil {
	panic(err)
}

fmt.Println(string(newJSON))

注意事项

  1. ajson 使用标准的 JSONPath 语法,但有一些扩展功能
  2. 错误处理很重要,特别是在处理动态 JSON 结构时
  3. 对于大型 JSON 文档,考虑性能影响
  4. ajson 支持大多数 JSONPath 操作符,包括递归下降(..)、通配符(*)等

ajson 库提供了强大而灵活的方式来处理 JSON 数据,特别适合需要复杂查询和修改的场景。相比标准库的 encoding/json,它更适合需要 JSONPath 功能的应用程序。

回到顶部