Super easy to parse JSON package GJSON with Golang

Basic overview

People who have used dynamic languages believe that it is very easy to parse JSON. You only need a few lines of code to get the parsed JSON object. For example, Python parses JSON as follows

result = {"name": "Bob", "age": 18}

print(result["name"])  // "Bob"

The simple data structure in Golang language can use map[string]interface {}, but if the JSON nesting format is too complex, it is particularly easy to get around in this way. If the struct structure is defined in advance, when the data is parsed into the structure with json.Unmarshal, the corresponding data is taken out, and the code amount will increase a lot. As shown below

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)

After comparison, I found that there is a relatively simple way for GoLang to parse JSON. As a language with such a wide audience, there must be a solution. Here is a grand introduction to the third-party package GJSON.

GJSON is a Go package that provides a quick and easy way to get values from JSON documents. It has functions such as single line retrieval, point symbol path, iteration and parsing JSON lines. Project address: https://github.com/tidwall/gjson

GJSON gets the value of JSON, which is relatively simple. The operation is as follows

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
}

After comparison, it is found that the way GJSON obtains values is relatively simple. GJSON also supports simple path syntax to get values. The specific usage will be described in detail below

install

To use GSON, you first need to install Go and then execute the following go get command

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

Usage mode

1, Path syntax

The following is a quick overview of path syntax, see GJSON syntax for more complete information. A path is a series of keys separated by points. ==The key may contain special wildcards' * 'and'? '. To access array values, use the index as the key. To get the number of elements in the array or access the subpath, use the # character. Points and wildcards can be escaped with '\'. = =

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() {
	// Get a value
	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"]

	// Total number of children array (number of elements)
	value5 := gjson.Get(json, "children.#")
	fmt.Println(value5) // 3

	// The second element of the children array
	value6 := gjson.Get(json, "children.1")
	fmt.Println(value6) // Alex

	// *Represents any character (including 0)
	value7 := gjson.Get(json, "child*.2")
	fmt.Println(value7) // Jack

	// ? represents 0 or 1 characters
	value8 := gjson.Get(json, "c?ildren.0")
	fmt.Println(value8) // Sara

	// \Meaning escape
	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

	/*
	You can also use # (...) to query the first match in the array, or use # (...) # - to find all matches.
	Query support = =,! =, < and < =, > and > = comparison operators, as well as simple pattern matching support of% (similar) and!% (not similar).
	*/

	// Find the first match in the array
	value12 := gjson.Get(json, `friends.#(last=="Murphy").first`)
	fmt.Println(value12) // Dale

	// Find all matches in the array
	value13 := gjson.Get(json, `friends.#(last=="Murphy")#.first`)
	fmt.Println(value13) // ["Dale","Jane"]

	// Find all last values with age greater than 45 in friends array
	value14 := gjson.Get(json, `friends.#(age>45)#.last`)
	fmt.Println(value14) // ["Craig","Murphy"]

	// The first value in the friends array is the last value starting with D
	value15 := gjson.Get(json, `friends.#(first%"D*").last`)
	fmt.Println(value15) // Murphy

	// The first value in the friends array is the last value that does not start with D
	value16 := gjson.Get(json, `friends.#(first!%"D*").last`)
	fmt.Println(value16) // Craig

	// Find the value of nets array in friends array contains fb
	value17 := gjson.Get(json, `friends.#(nets.#(=="fb"))#.first`)
	fmt.Println(value17) // ["Dale","Roger"]

}

2, Get nested array value

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(){
	// Get nested array value
	result := gjson.Get(json, "programmers.#.lastName")
	for _, name := range result.Array() {
		println(name.String()) //  prints McLaughlin  Hunter  Harold
	}
	
	// Querying objects in an array
	name := gjson.Get(json, `programmers.#(lastName="Hunter").firstName`)
	println(name.String())  // prints "Elliotte"
	
	// Traversal object or array
	result.ForEach(func(key, value gjson.Result) bool {
		println(value.String())
		return true // keep iterating
	})

	

3, Simple use of Parse and Get

The following three writing methods will get the same result

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

	// Check whether the value already exists
	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
	}
	
	// Verify whether it is json
	if !gjson.Valid(json) {
		fmt.Println("invalid json")
		return
	}
	value4 := gjson.Get(json, "name.last")
	fmt.Println(value4)

	// Decoding json string into 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]]]

	// Get multiple values at a time
	results := gjson.GetMany(json, "name.first", "name.last", "age")
	fmt.Println(results) // [Janet Prichard 47]
}

4, Work in bytes

If your JSON is included in the [] byte slice, the GetBytes function exists. This is better than get (string (data, path)

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

If you are using the gjson.GetBytes(json, path) function and want to avoid converting result.Raw to [] bytes, you can use the following pattern:

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)
}

performance

Performance test of GJSON and encoding/json,ffjson,EasyJSON, jsonparser and 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 data is used as follows

{
  "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;"
    }
  }
}    

Each operation rotates through one of the following search paths:

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

These benchmarks are based on the MacBook Pro 15 "2.8 GHz Intel Core i7 and go version 1.8

This article is translated from GJSON's Readme document. Each Demo has been tested. If you have any questions, please contact us

Keywords: Programming JSON github Python encoding

Added by youropensource on Fri, 27 Dec 2019 10:58:09 +0200