A query library for retrieving part of JSON based on JSONPath syntax.

AsaiYusuke/JSONPath

Build Status Go Report Card Coverage Status Go Reference Awesome Go
License: MIT

AsaiYusuke/JSONPath

This Go library is for retrieving a part of JSON according to the JSONPath query syntax.

The core JSONPath syntax on which this library based:

Note:

For syntax compatibility among other libraries, please check πŸ“ my comparison results.

Table of Contents

Getting started

Install:

go get github.com/AsaiYusuke/jsonpath

Simple example:

package main

import (
  "encoding/json"
  "fmt"

  "github.com/AsaiYusuke/jsonpath"
)

func main() {
  jsonPath, srcJSON := `$.key`, `{"key":"value"}`
  var src interface{}
  json.Unmarshal([]byte(srcJSON), &src)
  output, _ := jsonpath.Retrieve(jsonPath, src)
  outputJSON, _ := json.Marshal(output)
  fmt.Println(string(outputJSON))
  // Output:
  // ["value"]
}

Basic design

Ease of development

  • PEG separated the JSONPath syntax analyzer from functionality itself to simplify the source.
  • Equipped with a large number of unit tests to avoid bugs that lead to unexpected results.

Ease of use

  • The error specification enables the library user to handle errors correctly.

Compatibility

How to use

* Retrieve one-time or repeated

The Retrieve function returns retrieved result using JSONPath and JSON object:

output, err := jsonpath.Retrieve(jsonPath, src)

πŸ“ Example

The Parse function returns a parser-function that completed to check JSONPath syntax. By using parser-function, it can repeat to retrieve with the same JSONPath :

jsonPath, err := jsonpath.Parse(jsonPath)
output1, err1 := jsonPath(src1)
output2, err2 := jsonPath(src2)
:

πŸ“ Example

* Error handling

If there is a problem with the execution of APIs, an error type returned. These error types define the corresponding symptom, as listed below:

Syntax check errors from Retrieve, Parse

Error type Message format Symptom Ex
ErrorInvalidSyntax invalid syntax (position=%d, reason=%s, near=%s) The invalid syntax found in the JSONPath.
The reason including in this message will tell you more about it.
πŸ“
ErrorInvalidArgument invalid argument (argument=%s, error=%s) The argument specified in the JSONPath treated as the invalid error in Go syntax. πŸ“
ErrorFunctionNotFound function not found (function=%s) The function specified in the JSONPath is not found. πŸ“
ErrorNotSupported not supported (feature=%s, path=%s) The unsupported syntaxes specified in the JSONPath. πŸ“

Runtime errors from Retrieve, parser-functions

Error type Message format Symptom Ex
ErrorMemberNotExist member did not exist (path=%s) The object/array member specified in the JSONPath did not exist in the JSON object. πŸ“
ErrorTypeUnmatched type unmatched (expected=%s, found=%s, path=%s) The node type specified in the JSONPath did not exist in the JSON object. πŸ“
ErrorFunctionFailed function failed (function=%s, error=%s) The function specified in the JSONPath failed. πŸ“

The type checking is convenient to recognize which error happened.

  :
  _,err := jsonpath.Retrieve(jsonPath, srcJSON)
  if err != nil {
    switch err.(type) {
    case jsonpath.ErrorMemberNotExist:
      fmt.printf(`retry with other srcJSON: %v`, err)
      continue
    case jsonpath.ErrorInvalidArgumentFormat:
      return nil, fmt.errorf(`specified invalid argument: %v`, err)
    }
    :
  }

* Function syntax

Function enables to format results by using user defined functions. The function syntax comes after the JSONPath.

There are two ways to use function:

Filter function

The filter function applies a user function to each values in the result to get converted.

πŸ“ Example

Aggregate function

The aggregate function converts all values in the result into a single value.

πŸ“ Example

* Accessing JSON

You can get the accessors ( Getters / Setters ) of the input JSON instead of the retrieved values. These accessors can use to update for the input JSON.

This feature can get enabled by giving Config.SetAccessorMode().

πŸ“ Example

Note:

It is not possible to use Setter for some results, such as for JSONPath including function syntax.

Also, operations using accessors follow the map/slice manner of Go language. If you use accessors after changing the structure of JSON, you need to pay attention to the behavior. If you don't want to worry about it, get the accessor again every time you change the structure.

Differences

Some behaviors that differ from the consensus exists in this library. For the entire comparisons, please check πŸ“ this result.

These behaviors will change in the future if appropriate ones found.

Character types

The following character types can be available for identifiers in dot-child notation.

Character type Availabe Escape
* Numbers and alphabets (0-9 A-Z a-z)
* Hyphen and underscore (- _)
* Non-ASCII Unicode characters (0x80 - 0x10FFFF)
Yes No
* Other printable symbols (Space ! " # $ % & ' ( ) * + , . / : ; < = > ? @ [ \ ] ^ ` { | } ~) Yes Yes
* Control code characters (0x00 - 0x1F, 0x7F) No -

The printable symbols except hyphen and underscore can use by escaping them.

JSONPath : $.abc\.def
srcJSON  : {"abc.def":1}
Output   : [1]

Wildcard in qualifier

The wildcards in qualifier can specify as a union of subscripts.

JSONPath : $[0,1:3,*]
srcJSON  : [0,1,2,3,4,5]
Output   : [0,1,2,0,1,2,3,4,5]

Regular expression

The regular expression syntax works as a regular expression in Go lang. In particular, you can use "(?i)" to specify the regular expression as the ignore case option.

JSONPath : $[?(@.a=~/(?i)CASE/)]
srcJSON  : ["Case","Hello"]
Output   : ["Case"]

JSONPaths in the filter-qualifier

JSONPaths that returns value group cannot specify with comparator or regular expression. But, existence check can use these.

JSONPaths that return a value group example
Recursive descent @..a
Multiple identifier @['a','b']
Wildcard identifier @.*
Slice qualifier @[0:1]
Wildcard qualifier @[*]
Union in the qualifier @[0,1]
Filter qualifier @.a[?(@.b)]
  • comparator example (error)
JSONPath : $[?(@..x == "hello world")]
srcJSON  : [{"a":1},{"b":{"x":"hello world"}}]
Error    : ErrorInvalidSyntax
  • regular expression example (error)
JSONPath : $[?(@..x=~/hello/)]
srcJSON  : [{"a":1},{"b":{"x":"hello world"}}]
Error    : ErrorInvalidSyntax
  • existence check example
JSONPath : $[?(@..x)]
srcJSON  : [{"a":1},{"b":{"x":"hello world"}}]
Output   : [{"b":{"x":"hello world"}}]

Benchmarks

I benchmarked two JSONPaths using several libraries for the Go language. What is being measured is the cost per job for a job that loops a lot after all the prep work done.

There was a performance differences. But if the number of queries is little, there will not be a big difference between any of them.

Also, the results will vary depending on the data entered. So this benchmark is for information only and should be re-measured at every time.

JSONPath for comparison with more libraries

This is the result of a JSONPath that all libraries were able to process. Oliveagle/jsonpath is fastest.

JSONPath : $.store.book[0].price

cpu: Intel(R) Core(TM) i5-6267U CPU @ 2.90GHz
BenchmarkAsaiYusukeJSONPath_threeLevelsWithIndex-4         	 5846179	       211.3 ns/op	      48 B/op	       3 allocs/op
BenchmarkOhler55Ojg_threeLevelsWithIndex-4                 	 1721571	       689.4 ns/op	    1040 B/op	       2 allocs/op
BenchmarkBhmjJSONSlice_threeLevelsWithIndex-4              	  687261	      1844 ns/op	      24 B/op	       1 allocs/op
BenchmarkPaesslerAGJSONPath_threeLevelsWithIndex-4         	 1892106	       639.2 ns/op	     208 B/op	       7 allocs/op
BenchmarkOliveagleJsonpath_threeLevelsWithIndex-4          	13973798	        85.53 ns/op	       0 B/op	       0 allocs/op

A slightly complex JSONPath

Libraries that can handle complex syntax limited to a few. Among these libraries, my library is fastest.

JSONPath : $..book[?(@.price > $.store.bicycle.price)]

cpu: Intel(R) Core(TM) i5-6267U CPU @ 2.90GHz
BenchmarkAsaiYusukeJSONPath_recursiveDescentWithFilter-4   	  400609	      3002 ns/op	     464 B/op	      20 allocs/op
BenchmarkOhler55Ojg_recursiveDescentWithFilter-4           	  247669	      5094 ns/op	    5240 B/op	      20 allocs/op
BenchmarkBhmjJSONSlice_recursiveDescentWithFilter-4        	   59060	     20075 ns/op	    2936 B/op	      57 allocs/op
BenchmarkPaesslerAGJSONPath                                	  not supported
BenchmarkOliveagleJsonpath                                 	  not supported
JSON used for the benchmark measurement:
Show
{ "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
  }
  }
}
Benchmark environment:
Show
Processor  : Intel Core i5-6267U 2.90GHz
Memory     : 16.0 GB
OS         : Windows 10
Go version : go1.16 windows/amd64

Project progress

  • Syntax
    • Identifier
      • identifier in dot notations
      • identifier in bracket notations
      • wildcard
      • multiple-identifier in bracket
      • recursive retrieve
    • Qualifier
      • index
      • slice
      • wildcard
      • Filter
        • logical operation
        • comparator
        • JSONPath retrieve in filter
      • script
    • Function
      • filter
      • aggregate
    • Refer to the consensus behaviors
  • Archtecture
    • PEG syntax analyzing
    • Error handling
    • Function
    • Accessing JSON
  • Go language manner
    • retrieve with the object in interface unmarshal
    • retrieve with the json.Number type
  • Source code
    • Release version
    • Unit tests
      • syntax tests
      • benchmark
      • coverage >80%
    • Examples
    • CI automation
    • Documentation
      • README
      • API doc
    • comparison result (local)
  • Development status
    • determine requirements / functional design
    • design-based coding
    • testing
    • documentation
  • Future ToDo
    • Refer to the something standard
    • Go language affinity
      • retrieve with the object in struct unmarshal
      • retrieve with the struct tags
      • retrieve with the user defined objects
Similar Resources

A K8s ClusterIP HTTP monitoring library based on eBPF

Owlk8s Seamless RED monitoring of k8s ClusterIP HTTP services. This library provides RED (rate,error,duration) monitoring for all(by default but exclu

Jun 16, 2022

A simple webdev utility program that allows developers to quickly validate and format JSON code

Toolbox CLI A simple webdev utility program that allows developers to quickly validate and format JSON code, convert from UNIX epoch to timestamp and

Jan 4, 2022

A golang tool to list out all EKS clusters with active nodegroups in all regions in json format

eks-tool A quick and dirty tool to list out all EKS clusters with active nodegro

Dec 18, 2021

go-opa-validate is an open-source lib that evaluates OPA (open policy agent) policy against JSON or YAML data.

go-opa-validate is an open-source lib that evaluates OPA (open policy agent) policy against JSON or YAML data.

go-opa-validate go-opa-validate is an open-source lib that evaluates OPA (open policy agent) policy against JSON or YAML data. Installation Usage Cont

Nov 17, 2022

Rqlite-recover - k8 controller to create recover json for rqlite cluster nodes when needed.

Cluster Recover for RQLite running on a k8s cluster The goal is to be able to recover a rqlite cluster when the majority of nodes get re-schedule to d

Sep 8, 2022

Frep - Generate file using template from environment, arguments, json/yaml/toml config files

frep Generate file using template from environment, arguments, json/yaml/toml config files. NAME: frep - Generate file using template USAGE: fr

Nov 30, 2022

Json-log-exporter - A Nginx log parser exporter for prometheus metrics

json-log-exporter A Nginx log parser exporter for prometheus metrics. Installati

Jan 5, 2022

Dotnet-appsettings-env - Convert .NET appsettings.json file to Kubernetes, Docker and Docker-Compose environment variables

dotnet-appsettings-env Convert .NET appsettings.json file to Kubernetes, Docker

Dec 30, 2022

Dotnet-appsettings-env - Convert .NET appsettings.json file to Kubernetes, Docker and Docker-Compose environment variables

dotnet-appsettings-env Convert .NET appsettings.json file to Kubernetes, Docker

Feb 16, 2022
Comments
  • Question: FilterFunction with extra arguments

    Question: FilterFunction with extra arguments

    I am trying to filter based on an array containing a given string.

    Working Test

    package targetsubscriptions
    
    import (
    	"encoding/json"
    	"fmt"
    	"reflect"
    	"testing"
    
    	"github.com/AsaiYusuke/jsonpath"
    )
    
    type events struct {
    }
    
    func TestContains(t *testing.T) {
    	srcJSON := `{
    		"metadata": {
    			"mfg": "seimens",
    			"type": "sensor",
    			"sub_type": "temperature",
    			"model": "K-32-abcd",
    			"tags": ["a", "b", "c"]
    		},
    		"data": {
    			"a":"b"
    		}
    	}`
    	var src interface{}
    	json.Unmarshal([]byte(srcJSON), &src)
    	var events []interface{}
    	events = append(events, src)
    
    	config := jsonpath.Config{}
    	config.SetFilterFunction(`contains`, func(param interface{}) (interface{}, error) {
    		if stringParam, ok := param.(string); ok {
    			if stringParam == "a" {
    				return stringParam, nil
    			}
    			return nil, fmt.Errorf(`not found`)
    		}
    		return nil, fmt.Errorf(`type error`)
    	})
    
    	jsonPath := `$[?(@.metadata.tags[*].contains())]`
    
    	output, err := jsonpath.Retrieve(jsonPath, events, config)
    	if err != nil {
    		fmt.Printf(`%v, %v`, reflect.TypeOf(err), err)
    		return
    	}
    	outputJSON, _ := json.Marshal(output)
    	fmt.Println(string(outputJSON))
    }
    
    

    I have my contains func hard coded to look for "a" for the test.

    is there anyway that I can write my jsonpath as follows;

    jsonPath := `$[?(@.metadata.tags[*].contains(a))]`
    

    or better yet as described as described here => jsonpath-examples-expression-cheetsheet ;

    jsonPath := `$[?(@.metadata.tags[*]=='a')]`
    

    where the implementation might look like this;

    
    func contains(param interface{}, extra... interface{}) (interface{}, error) {
     
    }
    
  • Panic on filter query

    Panic on filter query

    A panic happens when using a literal compare in a boolean logical operation

    To Reproduce version: df1774fbf7cda6364704fda7c6493ab13907a2f8 Att the following test cases to TestRetrieve_filterExist:

                         {
    				jsonpath:     `$[?(@.a>1 && (1==2))]`,
    				inputJSON:    `[{"a":1},{"a":2},{"a":3}]`,
    				expectedJSON: `[]`,
    			},  
    			  {
    				jsonpath:     `$[?(@.a>1 && (1==1))]`,
    				inputJSON:    `[{"a":1},{"a":2},{"a":3}]`,
    				expectedJSON: `[{"a":2},{"a":3}]`,
    			},  
    

    then run go test . -run TestRetrieve_filterExist

    --- FAIL: TestRetrieve_filterExist (0.00s) --- FAIL: TestRetrieve_filterExist/child_<$[?(@.a>1_&&_(1==2))]>_<[{"a":1},{"a":2},{"a":3}]> (0.00s) panic: runtime error: index out of range [1] with length 1 [recovered] panic: runtime error: index out of range [1] with length 1

    goroutine 567 [running]: testing.tRunner.func1.2({0x11919a0, 0xc000182630}) /usr/local/Cellar/go/1.18/libexec/src/testing/testing.go:1389 +0x24e testing.tRunner.func1() /usr/local/Cellar/go/1.18/libexec/src/testing/testing.go:1392 +0x39f panic({0x11919a0, 0xc000182630}) /usr/local/Cellar/go/1.18/libexec/src/runtime/panic.go:838 +0x207 github.com/AsaiYusuke/jsonpath.(*syntaxLogicalAnd).compute(0xc0001ef8c0, {0x1171e40, 0xc00060eee8}, {0xc0001c75c0, 0x3, 0x4}, 0x12f66e0?) /go/src/jsonpath/syntax_query_logical_and.go:14 +0x130 github.com/AsaiYusuke/jsonpath.(*syntaxFilterQualifier).retrieveList(0xc00060efd8, {0x1171e40, 0xc00060eee8}, {0xc0001c75c0, 0x3, 0x4}, 0xc00060f008) /go/src/jsonpath/syntax_node_qualifier_filter.go:96 +0x62 github.com/AsaiYusuke/jsonpath.(*syntaxFilterQualifier).retrieve(0x97?, {0x1171e40?, 0xc00060eee8?}, {0x1171e40?, 0xc00060eee8?}, 0x0?) /go/src/jsonpath/syntax_node_qualifier_filter.go:19 +0x9e github.com/AsaiYusuke/jsonpath.Parse.func2({0x1171e40, 0xc00060eee8}) /go/src/jsonpath/jsonpath.go:57 +0x77 github.com/AsaiYusuke/jsonpath.Retrieve({0x11a7a90?, 0x1171e40?}, {0x1171e40, 0xc00060eee8}, {0x0?, 0x1178a20?, 0xc0006036d0?}) /go/src/jsonpath/jsonpath.go:18 +0x64 github.com/AsaiYusuke/jsonpath.execTestRetrieve(0xc0005ba820, {0x1171e40, 0xc00060eee8}, {{0x11a7a90, 0x15}, {0x11a95d6, 0x19}, {0x119fd44, 0x2}, {0x0, ...}, ...}) /go/src/jsonpath/test_jsonpath_test.go:85 +0x2db github.com/AsaiYusuke/jsonpath.execTestRetrieveTestGroups.func1(0xc0005ba820) /go/src/jsonpath/test_jsonpath_test.go:129 +0x1a5 testing.tRunner(0xc0005ba820, 0xc0005c2000) /usr/local/Cellar/go/1.18/libexec/src/testing/testing.go:1439 +0x102 created by testing.(*T).Run /usr/local/Cellar/go/1.18/libexec/src/testing/testing.go:1486 +0x35f FAIL github.com/AsaiYusuke/jsonpath 0.376s

    Expected behavior Test should not panic and pass

  • issue1 : Panic on filter query

    issue1 : Panic on filter query

    Issue #1

    Fixed a problem that caused abnormal termination for logical AND/OR operations involving literals during filtering.

    $[?(@.a>1 && (1==2))]
    
Fast, Docker-ready image processing server written in Go and libvips, with Thumbor URL syntax

Imagor Imagor is a fast, Docker-ready image processing server written in Go. Imagor uses one of the most efficient image processing library libvips (w

Dec 30, 2022
Search for HCL(v2) using syntax tree

hclgrep Search for HCL(v2) using syntax tree. The idea is heavily inspired by ht

Dec 12, 2022
Terraform-house - Golang Based terraform automation example using tf.json

Terraform House Manage your own terraform workflow using go language, with the b

Feb 17, 2022
k6 query generator for graphite API

xk6-carbonapi This is a k6 extension using the xk6 system. ❗ This is a proof of concept, isn't supported by the k6 team, and may break in the future.

Dec 1, 2022
Limited query interface to crt.sh

A crt.sh Utility This utility connects to the crt.sh database instead of their API which only supports minimal query types that return non-html (e.g.

Dec 16, 2021
A query server on Kubernetes resources

kql A query server on Kubernetes resources. Example curl command: # for query si

Jan 13, 2022
Solana Token Registry - a package that allows application to query for list of tokens

Please note: This repository is being rebuilt to accept the new volume of token additions and modifications. PR merges will be delayed. @solana/spl-to

Jan 16, 2022
A kubectl plugin to query multiple namespace at the same time.

kubemulti A kubectl plugin to query multiple namespace at the same time. $ kubemulti get pods -n cdi -n default NAMESPACE NAME

Mar 1, 2022
A kubectl plugin for easier query and operate k8s cluster.
A kubectl plugin for easier query and operate k8s cluster.

kube-query A kubectl plug-in that makes it easier to query and manipulate K8S clusters. (what is kubectl plug-in ?) Kube-query support some resource s

Jun 9, 2022
crud is a cobra based CLI utility which helps in scaffolding a simple go based micro-service along with build scripts, api documentation, micro-service documentation and k8s deployment manifests

crud crud is a CLI utility which helps in scaffolding a simple go based micro-service along with build scripts, api documentation, micro-service docum

Nov 29, 2021