Golang JSON解析包GJSON详解

和自甴很熟 提交于 2019-12-27 17:47:20

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

基本概述

相信使用过动态语言的人,都觉得解析JSON是很简单的,只需要简单的几行代码就可以拿到解析好的JSON对象。例如Python解析JSON如下所示

import json
jsonStr = '{"name": "Bob", "age": 18}'
result = json.loads(jsonStr)
print(result["name"]) // "Bob"

而Golang语言中简单的数据结构可使用map[string]interface{},但如果JSON嵌套格式太复杂,用这种方式特别容易绕晕,而如果预先定义struct结构体,在用json.Unmarshal把数据解析到结构体中,取出对应的数据,代码量编写会增加许多。如下所示

type Student struct {
	Name    string  `json:"name"`
	Age     int `json:"age"`
}

jsonData := []byte(`
{
    "name": "Bob",
    "age": 18
}`)

var student Student
err := json.Unmarshal(jsonData, &student)
if err != nil {
	fmt.Println(err)
}
fmt.Println(student.Name)

经过对比,发现GoLang解析JSON有没有相对简单的方法呢,作为受众这么广的语言,肯定是有解决方法的,这里就介绍一下今天要隆重介绍的第三方包GJSON。

GJSON是一个Go包,它提供了一种快速,简单的方法来从JSON文档中获取值。它具有诸如单行检索,点符号路径,迭代和解析JSON行之类的功能。项目地址: https://github.com/tidwall/gjson

GJSON 获得JSON的值,比较简单,操作如下

package main

import "github.com/tidwall/gjson"

const json = `{"name":{"first":"Janet","last":"Prichard"},"age":47}`

func main() {
	value := gjson.Get(json, "name.last")
	println(value.String())  // Prichard
}

经过对比,发现GJSON获得值的方式,相对简单许多。GJSON还支持简单的路径语法去获得值,具体使用方式下面会详细介绍

安装

要使用GSON, 首先需要安装Go然后执行如下go get命令

$ go get -u github.com/tidwall/gjson

使用方式

一、路径语法

以下是路径语法的快速概述,有关更多完整信息,请查看GJSON语法。路径是一系列由点分隔的键。==键可能包含特殊的通配符'*'和'?'。要访问数组值,请使用索引作为键。要获取数组中的元素数或访问子路径,请使用“#”字符。点和通配符可以用'\'进行转义。==

package main

import (
	"fmt"
	"github.com/tidwall/gjson"
)

const json = `
{
  "name": {"first": "Tom", "last": "Anderson"},
  "age":37,
  "children": ["Sara","Alex","Jack"],
  "fav.movie": "Deer Hunter",
  "friends": [
    {"first": "Dale", "last": "Murphy", "age": 44, "nets": ["ig", "fb", "tw"]},
    {"first": "Roger", "last": "Craig", "age": 68, "nets": ["fb", "tw"]},
    {"first": "Jane", "last": "Murphy", "age": 47, "nets": ["ig", "tw"]}
  ]
}
`

func main() {
	// 得到一个值
	value1 := gjson.Get(json, "age")
	fmt.Println(value1) // 37
	
	value2 := gjson.Get(json, "name.last")
	fmt.Println(value2) // Anderson

	value3 := gjson.Get(json, "name.first")
	fmt.Println(value3) // Tom

	value4 := gjson.Get(json, "children")
	fmt.Println(value4) // ["Sara","Alex","Jack"]

	// children数组的总数(元素个数)
	value5 := gjson.Get(json, "children.#")
	fmt.Println(value5) // 3

	// children数组的第二个元素
	value6 := gjson.Get(json, "children.1")
	fmt.Println(value6) // Alex

	// *表示任意个字符(包括0个)
	value7 := gjson.Get(json, "child*.2")
	fmt.Println(value7) // Jack

	// ?表示0个或1个字符
	value8 := gjson.Get(json, "c?ildren.0")
	fmt.Println(value8) // Sara

	// \ 表示转义
	value9 := gjson.Get(json, "fav\\.movie")
	fmt.Println(value9) // Deer Hunter

	value10 := gjson.Get(json, "friends.#.first")
	fmt.Println(value10) // ["Dale","Roger","Jane"]

	value11 := gjson.Get(json, "friends.1.last")
	fmt.Println(value11) // Craig

	/*
	也可以使用#(...)查询数组中的第一个匹配项,或使用#(...)# 查找所有匹配项。
	查询支持==,!=,<,<=,>,>= 比较运算符,以及支持%(类似)和!%(不类似)的简单模式匹配。
	*/

	// 查找数组中的第一个匹配项
	value12 := gjson.Get(json, `friends.#(last=="Murphy").first`)
	fmt.Println(value12) // Dale

	// 查找数组中的所有匹配项
	value13 := gjson.Get(json, `friends.#(last=="Murphy")#.first`)
	fmt.Println(value13) // ["Dale","Jane"]

	// 查找friends数组中所有age大于45的last值
	value14 := gjson.Get(json, `friends.#(age>45)#.last`)
	fmt.Println(value14) // ["Craig","Murphy"]

	// 查找friends数组中第一个first对应的值是以D开头的 last值
	value15 := gjson.Get(json, `friends.#(first%"D*").last`)
	fmt.Println(value15) // Murphy

	// 查找friends数组中第一个first对应的值是不以D开头的 last值
	value16 := gjson.Get(json, `friends.#(first!%"D*").last`)
	fmt.Println(value16) // Craig

	// 查找friends数组中nets数组值包含fb
	value17 := gjson.Get(json, `friends.#(nets.#(=="fb"))#.first`)
	fmt.Println(value17) // ["Dale","Roger"]

}

二、获取嵌套数组值

package main

import (
	"fmt"
	"github.com/tidwall/gjson"
)

const json  = `
{
  "programmers": [
    {
      "firstName": "Janet", 
      "lastName": "McLaughlin", 
    }, {
      "firstName": "Elliotte", 
      "lastName": "Hunter", 
    }, {
      "firstName": "Jason", 
      "lastName": "Harold", 
    }
  ]
}
`

func main(){
	// 获取嵌套数组值
	result := gjson.Get(json, "programmers.#.lastName")
	for _, name := range result.Array() {
		println(name.String()) //  prints McLaughlin  Hunter  Harold
	}
	
	// 查询数组中的对象
	name := gjson.Get(json, `programmers.#(lastName="Hunter").firstName`)
	println(name.String())  // prints "Elliotte"
	
	// 遍历对象或数组
	result.ForEach(func(key, value gjson.Result) bool {
		println(value.String())
		return true // keep iterating
	})

	

三、简单的Parse和Get使用

以下三种写法会得到相同的结果

package main

import (
	"fmt"
	"github.com/tidwall/gjson"
)

const json = `{"name":{"first":"Janet","last":"Prichard"},"age":47}`

func main(){
    value1 := gjson.Parse(json).Get("name").Get("last")
	fmt.Println(value1) // Prichard
	value2 := gjson.Get(json, "name").Get("last")
	fmt.Println(value2) // Prichard
	value3 := gjson.Get(json, "name.last")
	fmt.Println(value3)  // Prichard

	// 检测值是否已经存在
	value := gjson.Get(json, "name.last")
	if !value.Exists() {
		println("no last name")
	} else {
		println(value.String())  // Prichard
	}

	// Or as one step
	if gjson.Get(json, "name.last").Exists() {
		println("has a last name") // has a last name
	}
	
	// 验证是否为json
	if !gjson.Valid(json) {
		fmt.Println("invalid json")
		return
	}
	value4 := gjson.Get(json, "name.last")
	fmt.Println(value4)

	// 将json字符串解码到map中
	m, ok := gjson.Parse(json).Value().(map[string]interface{})
	if !ok {
		// not a map
		fmt.Println("not map")
	}
	fmt.Println(m) // map[programmers:[map[firstName:Janet lastName:McLaughlin] map[firstName:Elliotte lastName:Hunter] map[firstName:Jason lastName:Harold]]]

	// 一次获取多个值
	results := gjson.GetMany(json, "name.first", "name.last", "age")
	fmt.Println(results) // [Janet Prichard 47]
}

四、以字节方式工作

如果您的JSON包含在[]byte切片中,则存在GetBytes函数。这比Get(string(data),path)更好

var json []byte = ...
result := gjson.GetBytes(json, path)

如果您使用的是gjson.GetBytes(json,path)函数,并且想要避免将result.Raw转换为[]byte,则可以使用以下模式:

var json []byte = ...
result := gjson.GetBytes(json, path)
var raw []byte
if result.Index > 0 {
raw = json[result.Index:result.Index+len(result.Raw)]
} else {
raw = []byte(result.Raw)
}

性能

GJSON 和encoding/json,ffjson,EasyJSON, jsonparser和json-iterator 的性能测试

BenchmarkGJSONGet-8                  3000000        372 ns/op          0 B/op         0 allocs/op
BenchmarkGJSONUnmarshalMap-8          900000       4154 ns/op       1920 B/op        26 allocs/op
BenchmarkJSONUnmarshalMap-8           600000       9019 ns/op       3048 B/op        69 allocs/op
BenchmarkJSONDecoder-8                300000      14120 ns/op       4224 B/op       184 allocs/op
BenchmarkFFJSONLexer-8               1500000       3111 ns/op        896 B/op         8 allocs/op
BenchmarkEasyJSONLexer-8             3000000        887 ns/op        613 B/op         6 allocs/op
BenchmarkJSONParserGet-8             3000000        499 ns/op         21 B/op         0 allocs/op
BenchmarkJSONIterator-8              3000000        812 ns/op        544 B/op         9 allocs/op

JSON 数据使用如下

{
  "widget": {
    "debug": "on",
    "window": {
      "title": "Sample Konfabulator Widget",
      "name": "main_window",
      "width": 500,
      "height": 500
    },
    "image": { 
      "src": "Images/Sun.png",
      "hOffset": 250,
      "vOffset": 250,
      "alignment": "center"
    },
    "text": {
      "data": "Click Here",
      "size": 36,
      "style": "bold",
      "vOffset": 100,
      "alignment": "center",
      "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;"
    }
  }
}    

每个操作都通过以下搜索路径之一进行轮换:

widget.window.name
widget.image.hOffset
widget.text.onMouseUp

这些基准测试是基于MacBook Pro 15" 2.8 GHz Intel Core i7 和Go 1.8版本

本文翻译自GJSON的Readme文档,每个Demo都经过测试,有疑问可以反馈联系,另外GJSON通过对路径进行匹配,有兴趣的可以看一下JSONPATH相关文章:https://goessner.net/articles/JsonPath/

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!