jsonpath

package module
v0.1.2 Latest Latest
Warning

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

Go to latest
Published: Oct 28, 2024 License: MIT Imports: 3 Imported by: 6

README

RFC 9535 JSONPath in Go

The jsonpath package provides RFC 9535 JSONPath functionality in Go.

JSONPath Expressions

A brief overview of RFC [9535 JSONPath] syntax:

Syntax Element Description**
$ root node identifier
@ current node identifier (valid only within filter selectors)
[<selectors>] child segment: selects zero or more children of a node
.name shorthand for ['name']
.* shorthand for [*]
..[<selectors>] descendant segment: selects zero or more descendants of a node
..name shorthand for ..['name']
..* shorthand for ..[*]
'name' name selector: selects a named child of an object
* wildcard selector: selects all children of a node
3 index selector: selects an indexed child of an array (from 0)
0:100:5 array slice selector: start:end:step for arrays
?<logical-expr> filter selector: selects particular children using a logical expression
length(@.foo) function extension: invokes a function in a filter expression

Package Stability

The root jsonpath package is stable and ready for use. These are the main interfaces to the package.

The registry package is also stable, but exposes data types from the spec package that are still in flux. Argument data types may still change.

The parser package interface is also stable, but in general should not be used directly.

The spec package remains under active development, mainly refactoring, reorganizing, renaming, and documenting. Its interface therefore is not stable and should not be used for production purposes.

Copyright © 2024 David E. Wheeler

Documentation

Overview

Package jsonpath implements RFC 9535 JSONPath query expressions.

Example

Select all the authors of the books in a bookstore object.

package main

import (
	"encoding/json"
	"fmt"
	"log"

	"github.com/theory/jsonpath"
)

func main() {
	// Parse a jsonpath query.
	p, err := jsonpath.Parse(`$.store.book[*].author`)
	if err != nil {
		log.Fatal(err)
	}

	// Select values from unmarshaled JSON input.
	store := bookstore()
	result := p.Select(store)

	// Show the result.
	items, err := json.Marshal(result)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s\n", items)

}

// bookstore returns an unmarshaled JSON object.
func bookstore() any {
	src := []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": 399
		  }
		}
	  }`)

	var value any
	if err := json.Unmarshal(src, &value); err != nil {
		log.Fatal(err)
	}
	return value
}
Output:

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

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrPathParse = parser.ErrPathParse

ErrPathParse errors are returned for path parse errors.

Functions

This section is empty.

Types

type Option

type Option func(*Parser)

Option defines a parser option.

func WithRegistry

func WithRegistry(reg *registry.Registry) Option

WithRegistry configures a Parser with a function Registry, which may contain function extensions. See Parser for an example.

type Parser

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

Parser parses JSONPath strings into [*Path]s.

Example

Use the Parser to parse a collection of paths.

package main

import (
	"encoding/json"
	"fmt"
	"log"

	"github.com/theory/jsonpath"
)

func main() {
	// Create a new parser using the default function registry.
	parser := jsonpath.NewParser()

	// Parse a list of paths.
	paths := []*jsonpath.Path{}
	for _, path := range []string{
		"$.store.book[*].author",
		"$..author",
		"$.store..color",
		"$..book[2].author",
		"$..book[2].publisher",
		"$..book[?@.isbn].title",
		"$..book[?@.price<10].title",
	} {
		p, err := parser.Parse(path)
		if err != nil {
			log.Fatal(err)
		}
		paths = append(paths, p)
	}

	// Later, use the paths to select from JSON inputs.
	store := bookstore()
	for _, p := range paths {
		items := p.Select(store)
		array, err := json.Marshal(items)
		if err != nil {
			log.Fatal(err)
		}

		fmt.Printf("%s\n", array)
	}
}

// bookstore returns an unmarshaled JSON object.
func bookstore() any {
	src := []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": 399
		  }
		}
	  }`)

	var value any
	if err := json.Unmarshal(src, &value); err != nil {
		log.Fatal(err)
	}
	return value
}
Output:

["Nigel Rees","Evelyn Waugh","Herman Melville","J. R. R. Tolkien"]
["Nigel Rees","Evelyn Waugh","Herman Melville","J. R. R. Tolkien"]
["red"]
["Herman Melville"]
[]
["Moby Dick","The Lord of the Rings"]
["Sayings of the Century","Moby Dick"]
Example (FunctionExtension)

The second use case for the Parser is to provide a registry.Registry containing function extensions, as defined by the standard. This example creates a function named "first" that returns the first item in a list of nodes.

package main

import (
	"errors"
	"fmt"
	"log"

	"github.com/theory/jsonpath"
	"github.com/theory/jsonpath/registry"
	"github.com/theory/jsonpath/spec"
)

func main() {
	// Register the first function.
	reg := registry.New()
	err := reg.Register(
		"first",           // name
		spec.FuncValue,    // returns a single value
		validateFirstArgs, // parse-time validation defined below
		firstFunc,         // function defined below
	)
	if err != nil {
		log.Fatalf("Error %v", err)
	}

	// Create a parser with the registry that contains the extension.
	parser := jsonpath.NewParser(jsonpath.WithRegistry(reg))

	// Use the function to select lists that start with 6.
	path, err := parser.Parse("$[? first(@.*) == 6]")
	if err != nil {
		log.Fatalf("Error %v", err)
	}

	// Do any of these arrays start with 6?
	input := []any{
		[]any{1, 2, 3, 4, 5},
		[]any{6, 7, 8, 9},
		[]any{4, 8, 12},
	}
	result := path.Select(input)
	fmt.Printf("%v\n", result)
}

// validateFirstArgs validates that a single argument is passed to the first()
// function, and that it can be converted to [spec.PathNodes], so that first()
// can return the first node. It's called by the parser.
func validateFirstArgs(fea []spec.FunctionExprArg) error {
	if len(fea) != 1 {
		return fmt.Errorf("expected 1 argument but found %v", len(fea))
	}

	if !fea[0].ResultType().ConvertsTo(spec.PathNodes) {
		return errors.New("cannot convert argument to PathNodes")
	}

	return nil
}

// firstFunc defines the custom first() JSONPath function. It converts its
// single argument to a [spec.NodesType] value and returns a [*spec.ValueType]
// that contains the first node. If there are no nodes it returns nil.
func firstFunc(jv []spec.JSONPathValue) spec.JSONPathValue {
	nodes := spec.NodesFrom(jv[0])
	if len(nodes) == 0 {
		return nil
	}
	return spec.Value(nodes[0])
}
Output:

[[6 7 8 9]]

func NewParser

func NewParser(opt ...Option) *Parser

NewParser creates a new Parser configured by opt.

func (*Parser) MustParse

func (c *Parser) MustParse(path string) *Path

MustParse parses path, a JSON Path query string, into a Path. Panics with an ErrPathParse on parse failure.

func (*Parser) Parse

func (c *Parser) Parse(path string) (*Path, error)

Parse parses path, a JSON Path query string, into a Path. Returns an ErrPathParse on parse failure.

type Path

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

Path represents a RFC 9535 JSONPath query.

func MustParse

func MustParse(path string) *Path

MustParse parses path into a Path. Panics with an ErrPathParse on parse failure.

func New

func New(q *spec.PathQuery) *Path

New creates and returns a new Path consisting of q.

func Parse

func Parse(path string) (*Path, error)

Parse parses path, a JSONPath query string, into a Path. Returns an ErrPathParse on parse failure.

func (*Path) Query

func (p *Path) Query() *spec.PathQuery

Query returns p's root Query.

func (*Path) Select

func (p *Path) Select(input any) []any

Select returns the values that JSONPath query p selects from input.

func (*Path) String

func (p *Path) String() string

String returns a string representation of p.

Directories

Path Synopsis
internal
wasm
Package main performs a basic JSONPath query in order to test WASM compilation.
Package main performs a basic JSONPath query in order to test WASM compilation.
Package parser parses RFC 9535 JSONPath queries into parse trees.
Package parser parses RFC 9535 JSONPath queries into parse trees.
Package registry provides a RFC 9535 JSONPath function registry.
Package registry provides a RFC 9535 JSONPath function registry.
Package spec provides object definitions and execution for RFC 9535 JSONPath query expressions.
Package spec provides object definitions and execution for RFC 9535 JSONPath query expressions.

Jump to

Keyboard shortcuts

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