jsonpath

package module
v0.0.1-alpha Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 22, 2024 License: MIT Imports: 3 Imported by: 0

README

jsonpath

Developing Go implementation of JSONpath

This is a Go implementation of lexing and parsing of JSONpath, with a conventional split between lexical analysis (tokenising) and parsing (building from the token stream an abstract representation of a path sequence, with nested expression trees). That representation is converted into orders for a small abstract machine that evaluates a path expression given a root JSON value, yielding a collection of JSON values selected by the path expression.

It is currently in development and subject to change, but ready for friendly users.

The path image is from Joey Genovese on Unsplash.

Paths have the following grammar:

path ::= "$" step*
step ::= "." member | ".." member | "[" subscript "]" | ".." "[" subscript "]"
member ::= "*" | identifier | expr | signed-integer
subscript ::= subscript-expression | union-element ("," union-element)
subscript-expression ::= "*" | expr | filter
union-element ::=  array-index | string-literal | array-slice
array-index ::= signed-integer
array-slice ::= start? ":" end? (":" stride?)?
start ::= signed-integer | expr
end ::= signed-integer | expr
stride ::= signed-integer | expr
expr ::= "(" script-expression ")"
filter ::= "?(" script-expression ")"
step ::= ...  "[" subscript "]" ... | ".." "[" subscript "]"
subscript ::= subscript-expression | union-element ("," union-element)
subscript-expression ::= "*" | expr | filter
union-element ::=  array-index | string-literal | array-slice
array-index ::= signed-integer
array-slice ::= start? ":" end? (":" step?)?
member ::= "*" | identifier | expr | signed-integer
expr ::= "(" script-expression ")"
signed-integer ::= "-"? integer
integer ::= [0-9]+

Script expressions (filters and calculations) share the same syntax:

script-expression ::= e   // both filters and values share the same syntax
e ::= primary | e binary-op e | unary-op e
binary-op ::= "+" | "-" | "*" | "/" | "%" | "<" | ">" |
	">=" | "<=" | "==" | "!=" | "~" | "in" | "nin"  | "&&" | "||"
unary-op ::= "-" | "!"
unary ::= ("-" | "!")+ primary
primary ::= primary1 ("(" e-list? ")" | "[" e "]" | "." identifier)*
e-list ::= e ("," e)*
primary1 ::= identifier | integer | real | string |
		"/" re "/" | "@" | "$" | "(" e ")" | "[" e-list? "]"
re ::= <regular expression of some style, with \/ escaping the delimiting "/">
real ::= integer "." integer? ("e" [+-]? integer)?

The semantics and built-in functions are generally those of https://danielaparker.github.io/JsonCons.Net/articles/JsonPath/Specification.html — a rare example of specifying JSONPath systematically instead of providing a few examples — although this grammar is more restrictive (eg, as regards the content of a union expression). Some of its extensions (eg, the parent operator) are also not provided.

Documentation

Overview

Package jsonpath provides a parser and evaluator for JSONpaths, a syntax for expressing queries and locations in a JSON structure. Given a JSON value ("document") a path expression selects subcomponents and returns a list of the selected values.

The JSONpath syntax is often defined by providing a set of sample paths. This package instead is based on a grammar, parsed and interpreted in a conventional way.

Briefly, a JSONpath gives a dot-separated path through a JSON structure, with nested expressions providing dynamic values and filters. Unfortunately the notation has not yet been standardised, and implementations have signfiicant differences. This one aims to satisfy the existing consensus as represented by several test suites.

For the detailed syntax, run

go doc jsonpath/syntax

A given JSONpath expression (in textual form) is first transformed by Compile or MustCompile to a pointer to a JSONPath value that can then be applied repeatedly to JSON values using its Eval method. Compilation checks that the expression is valid JSONpath syntax as defined above.

Example
package main

import (
	"encoding/json"
	"fmt"
	"os"

	"github.com/forsyth/jsonpath"
)

var paths = []string{
	"$",
	"$.books",
	"$.books[?(@.author=='Adam Smith')].title",
	"$.books[0].author",
	"$.books[*].title",
}

var docs = []string{
	`{"books": [
		{"title": "Decline and Fall", "author": "Evelyn Waugh", "date": 1928},
		{"title": "Wealth of Nations", "author": "Adam Smith", "date": 1776}
	]}`,
}

func main() {
	for _, s := range paths {

		// ParsePath parses a JSON path expression into a Path: a sequence of Steps
		jpath, err := jsonpath.Compile(s)
		if err != nil {
			fmt.Fprintf(os.Stderr, "example: path %q: %s\n", s, err)
			continue
		}
		fmt.Printf("pattern: %s\n", s)
		for _, doc := range docs {
			var d interface{}
			err = json.Unmarshal([]byte(doc), &d)
			if err != nil {
				fmt.Fprintf(os.Stderr, "example: subject %q: %s\n", doc, err)
				continue
			}

			// jpath.Eval evaluates the path s on a given JSON value, the root of a document, returning
			// the list of resulting JSON values. The same JSONPath value can be reused, even concurrently.
			vals, err := jpath.Eval(d)
			if err != nil {
				fmt.Fprintf(os.Stderr, "example: path %q: subject %q: %s\n", s, doc, err)
				continue
			}
			fmt.Printf("subject: %s ->\n", doc)
			results, err := json.Marshal(vals)
			if err != nil {
				fmt.Printf("!%s\n", err)
				continue
			}
			fmt.Println("\t", string(results))
		}
	}
}
Output:

pattern: $
subject: {"books": [
		{"title": "Decline and Fall", "author": "Evelyn Waugh", "date": 1928},
		{"title": "Wealth of Nations", "author": "Adam Smith", "date": 1776}
	]} ->
	 [{"books":[{"author":"Evelyn Waugh","date":1928,"title":"Decline and Fall"},{"author":"Adam Smith","date":1776,"title":"Wealth of Nations"}]}]
pattern: $.books
subject: {"books": [
		{"title": "Decline and Fall", "author": "Evelyn Waugh", "date": 1928},
		{"title": "Wealth of Nations", "author": "Adam Smith", "date": 1776}
	]} ->
	 [[{"author":"Evelyn Waugh","date":1928,"title":"Decline and Fall"},{"author":"Adam Smith","date":1776,"title":"Wealth of Nations"}]]
pattern: $.books[?(@.author=='Adam Smith')].title
subject: {"books": [
		{"title": "Decline and Fall", "author": "Evelyn Waugh", "date": 1928},
		{"title": "Wealth of Nations", "author": "Adam Smith", "date": 1776}
	]} ->
	 ["Wealth of Nations"]
pattern: $.books[0].author
subject: {"books": [
		{"title": "Decline and Fall", "author": "Evelyn Waugh", "date": 1928},
		{"title": "Wealth of Nations", "author": "Adam Smith", "date": 1776}
	]} ->
	 ["Evelyn Waugh"]
pattern: $.books[*].title
subject: {"books": [
		{"title": "Decline and Fall", "author": "Evelyn Waugh", "date": 1928},
		{"title": "Wealth of Nations", "author": "Adam Smith", "date": 1776}
	]} ->
	 ["Decline and Fall","Wealth of Nations"]

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type JSONPath

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

JSONPath represents a compiled JSONpath expression. It is safe for concurrent use by goroutines.

func Compile

func Compile(expr string) (*JSONPath, error)

Compile parses a JSONpath expression and, if it is syntactically valid, returns a JSONPath value that allows repeated evaluation of that expression against a given JSON value. If the expression is not valid JSONPath, Compile instead returns only an error.

func MustCompile

func MustCompile(expr string) *JSONPath

MustCompile is like Compile but panics if the expression is invalid.

func (*JSONPath) Eval

func (path *JSONPath) Eval(root interface{}) ([]interface{}, error)

Eval evaluates a previously-compiled JSONpath expression against a given JSON value (the root of a document), as returned by encoding/json.Decoder.Unmarshal. It returns a slice containing the list of JSON values selected by the path expression. If a run-time error occurs, for instance an invalid dynamic regular expression, Eval stops and returns only an error. Eval may be used concurrently.

Path expressions contain boolean filter expressions of the form ?(expr), and other numeric, string or boolean expressions of the form (expr). The expression language is the same for each, containing a subset of JavaScript's expression and logical operators, and a match operator =~ (subject-string =~ /re/ or subject-string =~ regexp-string). The equality operators == and != apply JavaScript's equality rules. Boolean contexts apply JavaScript's falsy rules, and || and && also behave as in JavaScript. For instance (a || b) yields the value of b if a is falsy, not necessarily a Boolean value as in Go and other languages. JavaScript's implicit conversion rules are also applied when boolean, string and numeric values meet. Note that expressions also handle failure (eg, failing to find a key in an object, or selecting from a non-object) by propagating null values, which can then be detected and handled by using || and && as one might in JavaScript.

func (*JSONPath) String

func (path *JSONPath) String() string

String returns the source text used to compile the JSONpath expression.

Directories

Path Synopsis
cmd
Package mach implements a small abstract machine for path expressions, based on the representation produced by the sibling package paths.
Package mach implements a small abstract machine for path expressions, based on the representation produced by the sibling package paths.
Package jsonpath/paths provides a parser for JSONpaths, a syntax for expressing queries and locations in a JSON structure.
Package jsonpath/paths provides a parser for JSONpaths, a syntax for expressing queries and locations in a JSON structure.
The JSONpath syntax is typically defined by providing a set of sample paths.
The JSONpath syntax is typically defined by providing a set of sample paths.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL