GitHub - AsaiYusuke/jsonpath: A query library for retrieving part of JSON based on JSONPath syntax. (original) (raw)

AsaiYusuke/JSONPath

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

AsaiYusuke/JSONPath

This Go library allows you to extract parts of a JSON object using the JSONPath query syntax.

The core JSONPath syntax supported by this library is based on:

Note

For details on syntax compatibility with other libraries, see πŸ“ my comparison results.

Table of Contents

Getting started

Install

go get github.com/AsaiYusuke/jsonpath/v2

Simple example

package main

import ( "encoding/json" "fmt"

"github.com/AsaiYusuke/jsonpath/v2" )

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

Basic design

Streamlined Development

User-Friendly Interface

High Compatibility

How to use

* Retrieve one-time or repeatedly

The Retrieve function extracts values from a JSON object using a JSONPath expression:

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

πŸ“ Example

The Parse function returns a parser function that checks the JSONPath syntax in advance. You can use this parser function to repeatedly extract values with the same JSONPath:

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

πŸ“ Example

parser function

Example of buffer reuse:

parsed, _ := jsonpath.Parse("$[]") buf := make([]any, 0, 4) args := [][]any{&buf} out1, _ := parsed([]any{1}, args...) // writes into buf -> [1] out2, _ := parsed([]any{2}, args...) // reuses the same buf -> now [2] fmt.Println(out1) fmt.Println(out2) fmt.Println(buf) // Output: // [2] // [2] // [2]

πŸ“ Example

Note: Do not share the same buffer across goroutines concurrently.

* Error handling

If an error occurs during API execution, a specific error type is returned. The following error types help you identify the cause:

Syntax errors from Retrieve or Parse

Error type Message format Symptom Ex
ErrorInvalidSyntax invalid syntax (position=%d, reason=%s, near=%s) The JSONPath contains invalid syntax. The reason in the message provides more details. πŸ“
ErrorInvalidArgument invalid argument (argument=%s, error=%s) An argument in the JSONPath is invalid according to Go syntax. πŸ“
ErrorFunctionNotFound function not found (path=%s) The specified function in the JSONPath was not found. πŸ“
ErrorNotSupported not supported (path=%s, feature=%s) The JSONPath uses unsupported syntax. πŸ“

Runtime errors from Retrieve or parser functions

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

Type checking makes it easy to determine which error occurred.

import jsonpath "github.com/AsaiYusuke/jsonpath/v2" import errors "github.com/AsaiYusuke/jsonpath/v2/errors"

_, err := jsonpath.Retrieve(jsonPath, srcJSON) switch err.(type) { case errors.ErrorMemberNotExist: fmt.Printf("retry with other srcJSON: %v", err) // handle or continue case errors.ErrorInvalidArgument: return nil, fmt.Errorf("specified invalid argument: %v", err) }

* Function syntax

You can use user-defined functions to format results. The function syntax is appended after the JSONPath expression.

There are two types of functions:

Filter function

A filter function applies a user-defined function to each value in the result, transforming them individually.

πŸ“ Example

Aggregate function

An aggregate function combines all values in the result into a single value.

πŸ“ Example

* Accessing JSON

Instead of retrieving values directly, you can obtain accessors (Getters / Setters) for the input JSON. These accessors allow you to update the original JSON object.

Enable this feature by calling Config.SetAccessorMode().

πŸ“ Example

Accessor limitations

Setters are not available for some results, such as when using function syntax in the JSONPath.

Accessor operations follow Go's map/slice semantics. If you modify the structure of the JSON, be aware that accessors may not behave as expected. To avoid issues, obtain a new accessor each time you change the structure.

Differences

Some behaviors in this library differ from the consensus of other implementations. For a full comparison, see πŸ“ this result.

These behaviors may change in the future if more appropriate approaches are found.

Character types

The following character types are allowed for identifiers in dot-child notation:

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

Printable symbols (except hyphen and underscore) can be used by escaping them.

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

Wildcard in qualifier

Wildcards in qualifiers can be specified as a union with subscripts.

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

Regular expression

Regular expression syntax follows Go's regular expression rules. In particular, you can use "(?i)" to make the regular expression case-insensitive.

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

JSONPaths in the filter-qualifier

JSONPaths that return a value group cannot be used with a comparator or regular expression. However, you can use these syntaxes for existence checks.

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)]
JSONPath : $[?(@..x == "hello world")]
srcJSON  : [{"a":1},{"b":{"x":"hello world"}}]
Error    : ErrorInvalidSyntax
JSONPath : $[?(@..x=~/hello/)]
srcJSON  : [{"a":1},{"b":{"x":"hello world"}}]
Error    : ErrorInvalidSyntax
JSONPath : $[?(@..x)]
srcJSON  : [{"a":1},{"b":{"x":"hello world"}}]
Output   : [{"b":{"x":"hello world"}}]

If a JSONPath filter begins with the Root, it performs a whole-match operation if any match is found.

JSONPath : <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">[</mo><mo stretchy="false">?</mo><mo stretchy="false">(</mo></mrow><annotation encoding="application/x-tex">[?(</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">[</span><span class="mclose">?</span><span class="mopen">(</span></span></span></span>..x)]
srcJSON  : [{"a":1},{"b":{"x":"hello world"}}]
Output   : [{"a":1},{"b":{"x":"hello world"}}]

Benchmarks

Benchmark results for various Go JSONPath libraries (measured by myself) are available in the following repository:

Project progress