README

Abstract JSON

Build Status Go Report Card GoDoc Coverage Status Awesome

Abstract JSON is a small golang package provides a parser for JSON with support of JSONPath, in case when you are not sure in its structure.

Method Unmarshal will scan all the byte slice to create a root node of JSON structure, with all its behaviors.

Method Marshal will serialize current Node object to JSON structure.

Each Node has its own type and calculated value, which will be calculated on demand. Calculated value saves in atomic.Value, so it's thread safe.

Method JSONPath will returns slice of found elements in current JSON data, by JSONPath request.

Compare with other solutions

Check the cburgmer/json-path-comparison project.

Usage

Playground

package main

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

func main() {
	json := []byte(`...`)

	root, _ := ajson.Unmarshal(json)
	nodes, _ := root.JSONPath("$..price")
	for _, node := range nodes {
		node.SetNumeric(node.MustNumeric() * 1.25)
		node.Parent().AppendObject("currency", ajson.StringNode("", "EUR"))
	}
	result, _ := ajson.Marshal(root)

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

Console application

You can download ajson cli from the release page, or install from the source:

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

Usage:

Usage: ajson "jsonpath" ["input"]
  Read JSON and evaluate it with JSONPath.
Argument:
  jsonpath   Valid JSONPath or evaluate string (Examples: "$..[?(@.price)]", "$..price", "avg($..price)")
  input      Path to the JSON file. Leave it blank to use STDIN.

Examples:

  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 * $"

JSONPath

Current package supports JSONPath selection described at http://goessner.net/articles/JsonPath/.

JSONPath expressions always refer to a JSON structure in the same way as XPath expression are used in combination with an XML document. Since a JSON structure is usually anonymous and doesn't necessarily have a "root member object" JSONPath assumes the abstract name $ assigned to the outer level object.

JSONPath expressions can use the dot–notation

$.store.book[0].title

or the bracket–notation

$['store']['book'][0]['title']

for input paths. Internal or output paths will always be converted to the more general bracket–notation.

JSONPath allows the wildcard symbol * for member names and array indices. It borrows the descendant operator .. from E4X and the array slice syntax proposal [start:end:step] from ECMASCRIPT 4.

Expressions of the underlying scripting language (<expr>) can be used as an alternative to explicit names or indices as in

$.store.book[(@.length-1)].title

using the symbol @ for the current object. Filter expressions are supported via the syntax ?(<boolean expr>) as in

$.store.book[?(@.price < 10)].title

Here is a complete overview and a side by side comparison of the JSONPath syntax elements with its XPath counterparts.

JSONPath Description
$ the root object/element
@ the current object/element
. or [] child operator
.. recursive descent. JSONPath borrows this syntax from E4X.
* wildcard. All objects/elements regardless their names.
[] subscript operator. XPath uses it to iterate over element collections and for predicates. In Javascript and JSON it is the native array operator.
[,] Union operator in XPath results in a combination of node sets. JSONPath allows alternate names or array indices as a set.
[start:end:step] array slice operator borrowed from ES4.
?() applies a filter (script) expression.
() script expression, using the underlying script engine.

Script engine

Predefined constant

Package has several predefined constants.

 e       math.E     float64
 pi      math.Pi    float64
 phi     math.Phi   float64
 
 sqrt2     math.Sqrt2   float64
 sqrte     math.SqrtE   float64
 sqrtpi    math.SqrtPi  float64
 sqrtphi   math.SqrtPhi float64
 
 ln2     math.Ln2    float64
 log2e   math.Log2E  float64
 ln10    math.Ln10   float64
 log10e  math.Log10E float64
      
 true    true       bool
 false   false      bool
 null    nil        interface{}

You are free to add new one with function AddConstant:

    AddConstant("c", NumericNode("speed of light in vacuum", 299_792_458))
Examples
Using `true` in path

Playground

package main

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

func main() {
	json := []byte(`{"foo": [true, null, false, 1, "bar", true, 1e3], "bar": [true, "baz", false]}`)
	result, _ := ajson.JSONPath(json, `$..[?(@ == true)]`)
	fmt.Printf("Count of `true` values: %d", len(result))
}

Output:

Count of `true` values: 3
Using `null` in eval

Playground

package main

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

func main() {
	json := []byte(`{"foo": [true, null, false, 1, "bar", true, 1e3], "bar": [true, "baz", false]}`)
	result, _ := ajson.JSONPath(json, `$..[?(@ == true)]`)
	fmt.Printf("Count of `true` values: %d", len(result))
}

Output:

Count of `true` values: 3
Supported operations

Package has several predefined operators.

Operator precedence

Precedence    Operator
    6	    	  **
    5             *  /  %  <<  >>  &  &^
    4             +  -  |  ^
    3             ==  !=  <  <=  >  >= =~
    2             &&
    1             ||

Arithmetic operators

**   power                  integers, floats
+    sum                    integers, floats, strings
-    difference             integers, floats
*    product                integers, floats
/    quotient               integers, floats
%    remainder              integers

&    bitwise AND            integers
|    bitwise OR             integers
^    bitwise XOR            integers
&^   bit clear (AND NOT)    integers

<<   left shift             integer << unsigned integer
>>   right shift            integer >> unsigned integer

==  equals                  any
!=  not equals              any
<   less                    any
<=  less or equals          any
>   larger                  any
>=  larger or equals        any
=~  equals regex string     strings

You are free to add new one with function AddOperation:

	AddOperation("<>", 3, false, func(left *ajson.Node, right *ajson.Node) (node *ajson.Node, err error) {
		result, err := left.Eq(right)
		if err != nil {
			return nil, err
		}
		return BoolNode("neq", !result), nil
	})
Examples
Using `regex` operator

Playground

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())
	// Output:
	// JSON: {"name":"Foo","mail":"foo@example.com"}
}

Output:

JSON: {"name":"Foo","mail":"foo@example.com"}
Supported functions

Package has several predefined functions.

abs          math.Abs          integers, floats
acos         math.Acos         integers, floats
acosh        math.Acosh        integers, floats
asin         math.Asin         integers, floats
asinh        math.Asinh        integers, floats
atan         math.Atan         integers, floats
atanh        math.Atanh        integers, floats
avg          Average           array of integers or floats
cbrt         math.Cbrt         integers, floats
ceil         math.Ceil         integers, floats
cos          math.Cos          integers, floats
cosh         math.Cosh         integers, floats
erf          math.Erf          integers, floats
erfc         math.Erfc         integers, floats
erfcinv      math.Erfcinv      integers, floats
erfinv       math.Erfinv       integers, floats
exp          math.Exp          integers, floats
exp2         math.Exp2         integers, floats
expm1        math.Expm1        integers, floats
factorial    N!                unsigned integer
floor        math.Floor        integers, floats
gamma        math.Gamma        integers, floats
j0           math.J0           integers, floats
j1           math.J1           integers, floats
length       len               array
log          math.Log          integers, floats
log10        math.Log10        integers, floats
log1p        math.Log1p        integers, floats
log2         math.Log2         integers, floats
logb         math.Logb         integers, floats
not          not               any
pow10        math.Pow10        integer
round        math.Round        integers, floats
roundtoeven  math.RoundToEven  integers, floats
sin          math.Sin          integers, floats
sinh         math.Sinh         integers, floats
sum          Sum               array of integers or floats
sqrt         math.Sqrt         integers, floats
tan          math.Tan          integers, floats
tanh         math.Tanh         integers, floats
trunc        math.Trunc        integers, floats
y0           math.Y0           integers, floats
y1           math.Y1           integers, floats

You are free to add new one with function AddFunction:

	AddFunction("trim", func(node *ajson.Node) (result *Node, err error) {
		if node.IsString() {
			return StringNode("trim", strings.TrimSpace(node.MustString())), nil
		}
		return
	})
Examples
Using `avg` for array

Playground

package main

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

func main() {
	json := []byte(`{"prices": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}`)
	root, err := ajson.Unmarshal(json)
	if err != nil {
		panic(err)
	}
	result, err := ajson.Eval(root, `avg($.prices)`)
	if err != nil {
		panic(err)
	}
	fmt.Printf("Avg price: %0.1f", result.MustNumeric())
	// Output:
	// Avg price: 5.5
}

Output:

Avg price: 5.5

Examples

Calculating AVG(price) when object is heterogeneous.

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

Playground

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:

Playground

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

	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

Playground

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)
	}
	result, err := ajson.Eval(root, "avg($..price)")
	if err != nil {
		panic(err)
	}
	fmt.Println("AVG price:", result.MustNumeric())
}

Marshal

Playground

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)"))
	err := root.AppendObject("price(avg)", result)
	if err != nil {
		panic(err)
	}
	marshalled, err := ajson.Marshal(root)
	if err != nil {
		panic(err)
	}
	fmt.Printf("%s", marshalled)
}

Benchmarks

Current package is comparable with encoding/json package.

Test data:

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

JSONPath: $.store..price

$ go test -bench=. -cpu=1 -benchmem
goos: linux
goarch: amd64
pkg: github.com/spyzhov/ajson
BenchmarkUnmarshal_AJSON          138032              8762 ns/op            5344 B/op         95 allocs/op
BenchmarkUnmarshal_JSON           117423             10502 ns/op             968 B/op         31 allocs/op
BenchmarkJSONPath_all_prices       80908             14394 ns/op            7128 B/op        153 allocs/op

License

MIT licensed. See the LICENSE file for details.

Documentation

Overview

Package ajson implements decoding of JSON as defined in RFC 7159 without predefined mapping to a struct of golang, with support of JSONPath.

All JSON structs reflects to a custom struct of Node, witch can be presented by it type and value.

Method Unmarshal will scan all the byte slice to create a root node of JSON structure, with all it behaviors.

Each Node has it's own type and calculated value, which will be calculated on demand. Calculated value saves in atomic.Value, so it's thread safe.

Method JSONPath will returns slice of founded elements in current JSON data, by it's JSONPath.

JSONPath selection described at http://goessner.net/articles/JsonPath/

JSONPath expressions always refer to a JSON structure in the same way as XPath expression are used in combination with an XML document. Since a JSON structure is usually anonymous and doesn't necessarily have a "root member object" JSONPath assumes the abstract name $ assigned to the outer level object.

JSONPath expressions can use the dot–notation

$.store.book[0].title

or the bracket–notation

$['store']['book'][0]['title']

for input pathes. Internal or output pathes will always be converted to the more general bracket–notation.

JSONPath allows the wildcard symbol * for member names and array indices. It borrows the descendant operator '..' from E4X and the array slice syntax proposal [start:end:step] from ECMASCRIPT 4.

Expressions of the underlying scripting language (<expr>) can be used as an alternative to explicit names or indices as in

$.store.book[(@.length-1)].title

using the symbol '@' for the current object. Filter expressions are supported via the syntax ?(<boolean expr>) as in

$.store.book[?(@.price < 10)].title

Here is a complete overview and a side by side comparison of the JSONPath syntax elements with its XPath counterparts.

$          the root object/element
@          the current object/element
. or []  child operator
..      recursive descent. JSONPath borrows this syntax from E4X.
*       wildcard. All objects/elements regardless their names.
[]      subscript operator. XPath uses it to iterate over element collections and for predicates. In Javascript and JSON it is the native array operator.
[,]     Union operator in XPath results in a combination of node sets. JSONPath allows alternate names or array indices as a set.
[start:end:step]  array slice operator borrowed from ES4.
?()     applies a filter (script) expression.
()      script expression, using the underlying script engine.

JSONPath Script engine

Predefined constant

Package has several predefined constants. You are free to add new one with AddConstant

e       math.E     float64
pi      math.Pi    float64
phi     math.Phi   float64

sqrt2     math.Sqrt2   float64
sqrte     math.SqrtE   float64
sqrtpi    math.SqrtPi  float64
sqrtphi   math.SqrtPhi float64

ln2     math.Ln2    float64
log2e   math.Log2E  float64
ln10    math.Ln10   float64
log10e  math.Log10E float64

true    true       bool
false   false      bool
null    nil        interface{}

Supported operations

Package has several predefined operators. You are free to add new one with AddOperator

Operator precedence: https://golang.org/ref/spec#Operator_precedence

Precedence    Operator
6             **
5             *   /   %  <<  >>  &  &^
4             +   -   |  ^
3             ==  !=  <  <=  >  >=  =~
2             &&
1             ||

Arithmetic operators: https://golang.org/ref/spec#Arithmetic_operators

**   power                  integers, floats
+    sum                    integers, floats, strings
-    difference             integers, floats
*    product                integers, floats
/    quotient               integers, floats
%    remainder              integers

&    bitwise AND            integers
|    bitwise OR             integers
^    bitwise XOR            integers
&^   bit clear (AND NOT)    integers

<<   left shift             integer << unsigned integer
>>   right shift            integer >> unsigned integer

==  equals                  any
!=  not equals              any
<   less                    any
<=  less or equals          any
>   larger                  any
>=  larger or equals        any
=~  equals regex string     strings

Supported functions

Package has several predefined functions. You are free to add new one with AddFunction

abs          math.Abs          integers, floats
acos         math.Acos         integers, floats
acosh        math.Acosh        integers, floats
asin         math.Asin         integers, floats
asinh        math.Asinh        integers, floats
atan         math.Atan         integers, floats
atanh        math.Atanh        integers, floats
avg          Average           array of integers or floats
cbrt         math.Cbrt         integers, floats
ceil         math.Ceil         integers, floats
cos          math.Cos          integers, floats
cosh         math.Cosh         integers, floats
erf          math.Erf          integers, floats
erfc         math.Erfc         integers, floats
erfcinv      math.Erfcinv      integers, floats
erfinv       math.Erfinv       integers, floats
exp          math.Exp          integers, floats
exp2         math.Exp2         integers, floats
expm1        math.Expm1        integers, floats
factorial    N!                unsigned integer
floor        math.Floor        integers, floats
gamma        math.Gamma        integers, floats
j0           math.J0           integers, floats
j1           math.J1           integers, floats
length       len               array
log          math.Log          integers, floats
log10        math.Log10        integers, floats
log1p        math.Log1p        integers, floats
log2         math.Log2         integers, floats
logb         math.Logb         integers, floats
not          not               any
pow10        math.Pow10        integer
round        math.Round        integers, floats
roundtoeven  math.RoundToEven  integers, floats
sin          math.Sin          integers, floats
sinh         math.Sinh         integers, floats
sqrt         math.Sqrt         integers, floats
tan          math.Tan          integers, floats
tanh         math.Tanh         integers, floats
trunc        math.Trunc        integers, floats
y0           math.Y0           integers, floats
y1           math.Y1           integers, floats

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func AddConstant

func AddConstant(alias string, value *Node)

AddConstant add a constant for internal JSONPath script

Example
AddConstant("SqrtPi", NumericNode("SqrtPi", math.SqrtPi))
Output:

Example (Eval)
json := []byte(`{"radius": 50, "position": [56.4772531, 84.9918139]}`)
root, err := Unmarshal(json)
if err != nil {
	panic(err)
}
result, err := Eval(root, `2 * $.radius * pi`)
if err != nil {
	panic(err)
}
fmt.Printf("Circumference: %0.3f m.", result.MustNumeric())
Output:

Circumference: 314.159 m.
Example (Using)
json := []byte(`{"foo": [true, null, false, 1, "bar", true, 1e3], "bar": [true, "baz", false]}`)
result, err := JSONPath(json, `$..[?(@ == true)]`)
if err != nil {
	panic(err)
}
fmt.Printf("Count of `true` values: %d", len(result))
Output:

Count of `true` values: 3

func AddFunction

func AddFunction(alias string, function Function)

AddFunction add a function for internal JSONPath script

Example
AddFunction("array_sum", func(node *Node) (result *Node, err error) {
	if node.IsArray() {
		var (
			sum, num float64
			array    []*Node
		)
		array, err = node.GetArray()
		if err != nil {
			return nil, err
		}
		for _, child := range array {
			if !child.IsNumeric() {
				return nil, errors.New("wrong type")
			}
			num, err = child.GetNumeric()
			if err != nil {
				return
			}
			sum += num
		}
		return NumericNode("array_sum", sum), nil
	}
	return
})
Output:

Example (Usage)
json := []byte(`{"prices": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}`)
root, err := Unmarshal(json)
if err != nil {
	panic(err)
}
result, err := Eval(root, `avg($.prices)`)
if err != nil {
	panic(err)
}
fmt.Printf("Avg price: %0.1f", result.MustNumeric())
Output:

Avg price: 5.5

func AddOperation

func AddOperation(alias string, prior uint8, right bool, operation Operation)

AddOperation add an operation for internal JSONPath script

Example
AddOperation("<>", 3, false, func(left *Node, right *Node) (node *Node, err error) {
	res, err := left.Eq(right)
	if err != nil {
		return nil, err
	}
	return BoolNode("neq", !res), nil
})
Output:

Example (Regex)
json := []byte(`[{"name":"Foo","mail":"foo@example.com"},{"name":"bar","mail":"bar@example.org"}]`)
result, err := JSONPath(json, `$.[?(@.mail =~ '.+@example\\.com')]`)
if err != nil {
	panic(err)
}
fmt.Printf("JSON: %s", result[0].Source())
Output:

JSON: {"name":"Foo","mail":"foo@example.com"}

func Marshal

func Marshal(node *Node) (result []byte, err error)

Marshal returns slice of bytes, marshaled from current value

Example
data := []byte(`[{"latitude":1,"longitude":2},{"other":"value"},null,{"internal":{"name": "unknown", "longitude":22, "latitude":11}}]`)
root := Must(Unmarshal(data))
locations, _ := root.JSONPath("$..[?(@.latitude && @.longitude)]")
for _, location := range locations {
	name := fmt.Sprintf("At [%v, %v]", location.MustKey("latitude").MustNumeric(), location.MustKey("longitude").MustNumeric())
	_ = location.AppendObject("name", StringNode("", name))
}
result, _ := Marshal(root)
fmt.Printf("%s", result)
// JSON Output:
// [
// 	{
// 		"latitude":1,
// 		"longitude":2,
// 		"name":"At [1, 2]"
// 	},
// 	{
// 		"other":"value"
// 	},
// 	null,
// 	{
// 		"internal":{
// 			"name":"At [11, 22]",
// 			"longitude":22,
// 			"latitude":11
// 		}
// 	}
// ]
Output:

func ParseJSONPath

func ParseJSONPath(path string) (result []string, err error)

ParseJSONPath will parse current path and return all commands tobe run. Example:

result, _ := ParseJSONPath("$.store.book[?(@.price < 10)].title")
result == []string{"$", "store", "book", "?(@.price < 10)", "title"}

func Paths

func Paths(array []*Node) []string

Paths returns calculated paths of underlying nodes

Types

type Error

type Error struct {
	Type    ErrorType
	Index   int
	Char    byte
	Message string
}

Error is common struct to provide internal errors

func (Error) Error

func (err Error) Error() string

Error interface implementation

type ErrorType

type ErrorType int

ErrorType is container for reflection type of error

const (
	// WrongSymbol means that system found symbol than not allowed to be
	WrongSymbol ErrorType = iota
	// UnexpectedEOF means that data ended, leaving the node undone
	UnexpectedEOF
	// WrongType means that wrong type requested
	WrongType
	// WrongRequest means that wrong range requested
	WrongRequest
	// Unparsed means that json structure wasn't parsed yet
	Unparsed
)

type Function

type Function func(node *Node) (result *Node, err error)

Function - internal left function of JSONPath

type Node

type Node struct {
	// contains filtered or unexported fields
}

Node is a main struct, presents any type of JSON node. Available types are:

const (
	Null NodeType = iota
	Numeric
	String
	Bool
	Array
	Object
)

Every type has its own methods to be called. Every Node contains link to a byte data, parent and children, also calculated type of value, atomic value and internal information.

func ArrayNode

func ArrayNode(key string, value []*Node) (current *Node)

ArrayNode is constructor for Node with an Array value

func BoolNode

func BoolNode(key string, value bool) (current *Node)

BoolNode is constructor for Node with a Bool value

func Eval

func Eval(node *Node, cmd string) (result *Node, err error)

Eval evaluate expression `@.price == 19.95 && @.color == 'red'` to the result value i.e. Bool(true), Numeric(3.14), etc.

Example
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
      }
    ]
  }
}`)
root, err := Unmarshal(json)
if err != nil {
	panic(err)
}
result, err := Eval(root, "avg($..price)")
if err != nil {
	panic(err)
}
fmt.Print(result.MustNumeric())
Output:

14.774000000000001

func JSONPath

func JSONPath(data []byte, path string) (result []*Node, err error)

JSONPath returns slice of founded elements in current JSON data, by it's JSONPath.

JSONPath described at http://goessner.net/articles/JsonPath/

JSONPath expressions always refer to a JSON structure in the same way as XPath expression are used in combination with an XML document. Since a JSON structure is usually anonymous and doesn't necessarily have a "root member object" JSONPath assumes the abstract name $ assigned to the outer level object.

JSONPath expressions can use the dot–notation

$.store.book[0].title

or the bracket–notation

$['store']['book'][0]['title']

for input pathes. Internal or output pathes will always be converted to the more general bracket–notation.

JSONPath allows the wildcard symbol * for member names and array indices. It borrows the descendant operator '..' from E4X and the array slice syntax proposal [start:end:step] from ECMASCRIPT 4.

Expressions of the underlying scripting language (<expr>) can be used as an alternative to explicit names or indices as in

$.store.book[(@.length-1)].title

using the symbol '@' for the current object. Filter expressions are supported via the syntax ?(<boolean expr>) as in

$.store.book[?(@.price < 10)].title

Here is a complete overview and a side by side comparison of the JSONPath syntax elements with its XPath counterparts.

$          the root object/element
@          the current object/element
. or []  child operator
..      recursive descent. JSONPath borrows this syntax from E4X.
*       wildcard. All objects/elements regardless their names.
[]      subscript operator. XPath uses it to iterate over element collections and for predicates. In Javascript and JSON it is the native array operator.
[,]     Union operator in XPath results in a combination of node sets. JSONPath allows alternate names or array indices as a set.
[start:end:step]  array slice operator borrowed from ES4.
?()     applies a filter (script) expression.
()      script expression, using the underlying script engine.

JSONPath Script engine

Predefined constant

Package has several predefined constants. You are free to add new one with AddConstant

e       math.E     float64
pi      math.Pi    float64
phi     math.Phi   float64

sqrt2     math.Sqrt2   float64
sqrte     math.SqrtE   float64
sqrtpi    math.SqrtPi  float64
sqrtphi   math.SqrtPhi float64

ln2     math.Ln2    float64
log2e   math.Log2E  float64
ln10    math.Ln10   float64
log10e  math.Log10E float64

true    true       bool
false   false      bool
null    nil        interface{}

Supported operations

Package has several predefined operators. You are free to add new one with AddOperator

Operator precedence: https://golang.org/ref/spec#Operator_precedence

Precedence    Operator
6             **
5             *   /   %  <<  >>  &  &^
4             +   -   |  ^
3             ==  !=  <  <=  >  >=  =~
2             &&
1             ||

Arithmetic operators: https://golang.org/ref/spec#Arithmetic_operators

**   power                  integers, floats
+    sum                    integers, floats, strings
-    difference             integers, floats
*    product                integers, floats
/    quotient               integers, floats
%    remainder              integers

&    bitwise AND            integers
|    bitwise OR             integers
^    bitwise XOR            integers
&^   bit clear (AND NOT)    integers

<<   left shift             integer << unsigned integer
>>   right shift            integer >> unsigned integer

==  equals                  any
!=  not equals              any
<   less                    any
<=  less or equals          any
>   larger                  any
>=  larger or equals        any
=~  equals regex string     strings

Supported functions

Package has several predefined functions. You are free to add new one with AddFunction

abs          math.Abs          integers, floats
acos         math.Acos         integers, floats
acosh        math.Acosh        integers, floats
asin         math.Asin         integers, floats
asinh        math.Asinh        integers, floats
atan         math.Atan         integers, floats
atanh        math.Atanh        integers, floats
avg          Average           array of integers or floats
cbrt         math.Cbrt         integers, floats
ceil         math.Ceil         integers, floats
cos          math.Cos          integers, floats
cosh         math.Cosh         integers, floats
erf          math.Erf          integers, floats
erfc         math.Erfc         integers, floats
erfcinv      math.Erfcinv      integers, floats
erfinv       math.Erfinv       integers, floats
exp          math.Exp          integers, floats
exp2         math.Exp2         integers, floats
expm1        math.Expm1        integers, floats
factorial    N!                unsigned integer
floor        math.Floor        integers, floats
gamma        math.Gamma        integers, floats
j0           math.J0           integers, floats
j1           math.J1           integers, floats
length       len               array
log          math.Log          integers, floats
log10        math.Log10        integers, floats
log1p        math.Log1p        integers, floats
log2         math.Log2         integers, floats
logb         math.Logb         integers, floats
not          not               any
pow10        math.Pow10        integer
round        math.Round        integers, floats
roundtoeven  math.RoundToEven  integers, floats
sin          math.Sin          integers, floats
sinh         math.Sinh         integers, floats
sqrt         math.Sqrt         integers, floats
tan          math.Tan          integers, floats
tanh         math.Tanh         integers, floats
trunc        math.Trunc        integers, floats
y0           math.Y0           integers, floats
y1           math.Y1           integers, floats
Example
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
    }
  }
}`)
authors, err := JSONPath(json, "$.store.book[*].author")
if err != nil {
	panic(err)
}
for _, author := range authors {
	fmt.Println(author.MustString())
}
Output:

Nigel Rees
Evelyn Waugh
Herman Melville
J. R. R. Tolkien
Example (Array)
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
    }
  }
}`)
authors, err := JSONPath(json, "$.store.book[*].author")
if err != nil {
	panic(err)
}
result, err := Marshal(ArrayNode("", authors))
if err != nil {
	panic(err)
}
fmt.Println(string(result))
Output:

["Nigel Rees","Evelyn Waugh","Herman Melville","J. R. R. Tolkien"]

func Must

func Must(root *Node, err error) *Node

Must returns a Node if there was no error. Else - panic with error as the value.

Example
data := []byte(`{
        "Image": {
            "Width":  800,
            "Height": 600,
            "Title":  "View from 15th Floor",
            "Thumbnail": {
                "Url":    "http://www.example.com/image/481989943",
                "Height": 125,
                "Width":  100
            },
            "Animated" : false,
            "IDs": [116, 943, 234, 38793]
          }
      }`)

root := Must(Unmarshal(data))
fmt.Printf("Object has %d inheritors inside", root.Size())
Output:

Object has 1 inheritors inside
Example (Panic)
defer func() {
	if rec := recover(); rec != nil {
		fmt.Printf("Unmarshal(): %s", rec)
	}
}()
data := []byte(`{]`)

root := Must(Unmarshal(data))
fmt.Printf("Object has %d inheritors inside", root.Size())
Output:

Unmarshal(): wrong symbol ']' at 1

func NullNode

func NullNode(key string) *Node

NullNode is constructor for Node with Null value

func NumericNode

func NumericNode(key string, value float64) (current *Node)

NumericNode is constructor for Node with a Numeric value

func ObjectNode

func ObjectNode(key string, value map[string]*Node) (current *Node)

ObjectNode is constructor for Node with an Object value

func StringNode

func StringNode(key string, value string) (current *Node)

StringNode is constructor for Node with a String value

func Unmarshal

func Unmarshal(data []byte) (root *Node, err error)

Unmarshal parses the JSON-encoded data and return the root node of struct.

Doesn't calculate values, just type of stored value. It will store link to the data, on all life long.

Example

Calculate AVG price from different types of objects, JSON from: https://goessner.net/articles/JsonPath/index.html#e3

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 := 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)
}
Output:

Example (Unpack)

Unpack object interface and render html link. JSON from: https://tools.ietf.org/html/rfc7159#section-13

data := []byte(`{
        "Image": {
            "Width":  800,
            "Height": 600,
            "Title":  "View from 15th Floor",
            "Thumbnail": {
                "Url":    "http://www.example.com/image/481989943",
                "Height": 125,
                "Width":  100
            },
            "Animated" : false,
            "IDs": [116, 943, 234, 38793]
          }
      }`)

root, err := Unmarshal(data)
if err != nil {
	panic(err)
}
object, err := root.Unpack()
if err != nil {
	panic(err)
}
image := object.(map[string]interface{})["Image"].(map[string]interface{})
thumbnail := image["Thumbnail"].(map[string]interface{})
fmt.Printf(
	`<a href="%s?width=%.0f&height=%.0f" title="%s"><img src="%s?width=%.0f&height=%.0f" /></a>`,
	thumbnail["Url"],
	image["Width"],
	image["Height"],
	image["Title"],
	thumbnail["Url"],
	thumbnail["Width"],
	thumbnail["Height"],
)
Output:

func UnmarshalSafe

func UnmarshalSafe(data []byte) (root *Node, err error)

UnmarshalSafe do the same thing as Unmarshal, but copy data to the local variable, to make it editable.

func (*Node) AppendArray

func (n *Node) AppendArray(value ...*Node) error

AppendArray append current Array node values with Node values

func (*Node) AppendObject

func (n *Node) AppendObject(key string, value *Node) error

AppendObject append current Object node value with key:value

func (*Node) Clone

func (n *Node) Clone() *Node

Clone creates full copy of current Node. With all child, but without link to the parent.

Example
root := Must(Unmarshal(jsonPathTestData))
nodes, _ := root.JSONPath("$..price")
for i, node := range nodes {
	nodes[i] = node.Clone()
}

result, _ := Marshal(ArrayNode("", nodes))
fmt.Printf("Array: %s\n", result)

result, _ = Marshal(root)
fmt.Printf("Basic: %s\n", result)
Output:

Array: [19.95,8.95,12.99,8.99,22.99]
Basic: { "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
    }
  }
}

func (*Node) Delete

func (n *Node) Delete() error

Delete removes element from parent. For root - do nothing.

func (*Node) DeleteIndex

func (n *Node) DeleteIndex(index int) error

DeleteIndex removes element from Array, by it's index

func (*Node) DeleteKey

func (n *Node) DeleteKey(key string) error

DeleteKey removes element from Object, by it's key

func (*Node) DeleteNode

func (n *Node) DeleteNode(value *Node) error

DeleteNode removes element child

func (*Node) Empty

func (n *Node) Empty() bool

Empty method check if current container node has no children

func (*Node) Eq

func (n *Node) Eq(node *Node) (result bool, err error)

Eq check if nodes value are the same

func (*Node) Ge

func (n *Node) Ge(node *Node) (result bool, err error)

Ge check if nodes value is greater than given

func (*Node) Geq

func (n *Node) Geq(node *Node) (result bool, err error)

Geq check if nodes value is greater or equal than given

func (*Node) GetArray

func (n *Node) GetArray() (value []*Node, err error)

GetArray returns []*Node, if current type is Array, else: WrongType error

func (*Node) GetBool

func (n *Node) GetBool() (value bool, err error)

GetBool returns bool, if current type is Bool, else: WrongType error

func (*Node) GetIndex

func (n *Node) GetIndex(index int) (*Node, error)

GetIndex will return child node of current array node. If current node is not Array, or index is unavailable, will return error

func (*Node) GetKey

func (n *Node) GetKey(key string) (*Node, error)

GetKey will return child node of current object node. If current node is not Object, or key is unavailable, will return error

func (*Node) GetNull

func (n *Node) GetNull() (interface{}, error)

GetNull returns nil, if current type is Null, else: WrongType error

func (*Node) GetNumeric

func (n *Node) GetNumeric() (value float64, err error)

GetNumeric returns float64, if current type is Numeric, else: WrongType error

func (*Node) GetObject

func (n *Node) GetObject() (value map[string]*Node, err error)

GetObject returns map[string]*Node, if current type is Object, else: WrongType error

func (*Node) GetString

func (n *Node) GetString() (value string, err error)

GetString returns string, if current type is String, else: WrongType error

func (*Node) HasKey

func (n *Node) HasKey(key string) bool

HasKey will return boolean value, if current object node has custom key

func (*Node) Index

func (n *Node) Index() int

Index will return index of current node, please check, that parent of this node has an Array type

func (*Node) Inheritors

func (n *Node) Inheritors() (result []*Node)

Inheritors return sorted by keys/index slice of children

func (*Node) IsArray

func (n *Node) IsArray() bool

IsArray returns true if current node is Array

func (*Node) IsBool

func (n *Node) IsBool() bool

IsBool returns true if current node is Bool

func (*Node) IsDirty

func (n *Node) IsDirty() bool

IsDirty is the flag that shows, was node changed or not

func (*Node) IsNull

func (n *Node) IsNull() bool

IsNull returns true if current node is Null

func (*Node) IsNumeric

func (n *Node) IsNumeric() bool

IsNumeric returns true if current node is Numeric

func (*Node) IsObject

func (n *Node) IsObject() bool

IsObject returns true if current node is Object

func (*Node) IsString

func (n *Node) IsString() bool

IsString returns true if current node is String

func (*Node) JSONPath

func (n *Node) JSONPath(path string) (result []*Node, err error)

JSONPath evaluate path for current node

func (*Node) Key

func (n *Node) Key() string

Key will return key of current node, please check, that parent of this node has an Object type

func (*Node) Keys

func (n *Node) Keys() (result []string)

Keys will return count all keys of children of current node, please check, that parent of this node has an Object type

func (*Node) Le

func (n *Node) Le(node *Node) (result bool, err error)

Le check if nodes value is lesser than given

func (*Node) Leq

func (n *Node) Leq(node *Node) (result bool, err error)

Leq check if nodes value is lesser or equal than given

func (*Node) MustArray

func (n *Node) MustArray() (value []*Node)

MustArray returns []*Node, if current type is Array, else: panic if error happened

func (*Node) MustBool

func (n *Node) MustBool() (value bool)

MustBool returns bool, if current type is Bool, else: panic if error happened

func (*Node) MustIndex

func (n *Node) MustIndex(index int) (value *Node)

MustIndex will return child node of current array node. If current node is not Array, or index is unavailable, raise a panic

func (*Node) MustKey

func (n *Node) MustKey(key string) (value *Node)

MustKey will return child node of current object node. If current node is not Object, or key is unavailable, raise a panic

func (*Node) MustNull

func (n *Node) MustNull() (value interface{})

MustNull returns nil, if current type is Null, else: panic if error happened

func (*Node) MustNumeric

func (n *Node) MustNumeric() (value float64)

MustNumeric returns float64, if current type is Numeric, else: panic if error happened

func (*Node) MustObject

func (n *Node) MustObject() (value map[string]*Node)

MustObject returns map[string]*Node, if current type is Object, else: panic if error happened

func (*Node) MustString

func (n *Node) MustString() (value string)

MustString returns string, if current type is String, else: panic if error happened

func (*Node) Neq

func (n *Node) Neq(node *Node) (result bool, err error)

Neq check if nodes value are not the same

func (*Node) Parent

func (n *Node) Parent() *Node

Parent returns link to the parent of current node, nil for root

func (*Node) Path

func (n *Node) Path() string

Path returns full JsonPath of current Node

func (*Node) PopIndex

func (n *Node) PopIndex(index int) (node *Node, err error)

PopIndex removes element from Array, by it's index and return it

func (*Node) PopKey

func (n *Node) PopKey(key string) (node *Node, err error)

PopKey removes element from Object, by it's key and return it

func (*Node) SetArray

func (n *Node) SetArray(value []*Node) error

SetArray update current node value with Array value

func (*Node) SetBool

func (n *Node) SetBool(value bool) error

SetBool update current node value with Bool value

func (*Node) SetNull

func (n *Node) SetNull() error

SetNull update current node value with Null value

func (*Node) SetNumeric

func (n *Node) SetNumeric(value float64) error

SetNumeric update current node value with Numeric value

func (*Node) SetObject

func (n *Node) SetObject(value map[string]*Node) error

SetObject update current node value with Object value

func (*Node) SetString

func (n *Node) SetString(value string) error

SetString update current node value with String value

func (*Node) Size

func (n *Node) Size() int

Size will return count of children of current node, please check, that parent of this node has an Array type

func (*Node) Source

func (n *Node) Source() []byte

Source returns slice of bytes, which was identified to be current node

func (*Node) String

func (n *Node) String() string

String is implementation of Stringer interface, returns string based on source part

func (*Node) Type

func (n *Node) Type() NodeType

Type will return type of current node

func (*Node) Unpack

func (n *Node) Unpack() (value interface{}, err error)

Unpack will produce current node to it's interface, recursively with all underlying nodes (in contrast to Node.Value).

func (*Node) Value

func (n *Node) Value() (value interface{}, err error)

Value is calculating and returns a value of current node.

It returns nil, if current node type is Null.

It returns float64, if current node type is Numeric.

It returns string, if current node type is String.

It returns bool, if current node type is Bool.

It returns []*Node, if current node type is Array.

It returns map[string]*Node, if current node type is Object.

BUT! Current method doesn't calculate underlying nodes (use method Node.Unpack for that).

Value will be calculated only once and saved into atomic.Value.

type NodeType

type NodeType int32

NodeType is a kind of reflection of JSON type to a type of golang

const (
	// Null is reflection of nil.(interface{})
	Null NodeType = iota
	// Numeric is reflection of float64
	Numeric
	// String is reflection of string
	String
	// Bool is reflection of bool
	Bool
	// Array is reflection of []*Node
	Array
	// Object is reflection of map[string]*Node
	Object
)

Reflections:

Null    = nil.(interface{})
Numeric = float64
String  = string
Bool    = bool
Array   = []*Node
Object  = map[string]*Node

type Operation

type Operation func(left *Node, right *Node) (result *Node, err error)

Operation - internal script operation of JSONPath

Directories

Path Synopsis
cmd
Copy from https://github.com/freddierice/php_source/blob/467ed5d6edff72219afd3e644516f131118ef48e/ext/json/JSON_parser.c Base code: Copyright (c) 2005 JSON.org
Copy from https://github.com/freddierice/php_source/blob/467ed5d6edff72219afd3e644516f131118ef48e/ext/json/JSON_parser.c Base code: Copyright (c) 2005 JSON.org