jsonpath

package module
v0.10.0 Latest Latest
Warning

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

Go to latest
Published: Jul 11, 2025 License: MIT Imports: 5 Imported by: 6

README

RFC 9535 JSONPath in Go

The jsonpath package provides RFC 9535 JSONPath functionality in Go.

Learn More

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

Copyright © 2024-2025 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()
	nodes := p.Select(store)

	// Show the selected values.
	items, err := json.Marshal(nodes)
	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 LocatedNodeList added in v0.3.0

type LocatedNodeList []*spec.LocatedNode

LocatedNodeList is a list of nodes selected by a JSONPath query, along with their [NormalizedPath] locations. Returned by Path.SelectLocated.

Example
package main

import (
	"fmt"

	"github.com/theory/jsonpath"
)

func main() {
	// Load some JSON.
	menu := map[string]any{
		"apps": map[string]any{
			"guacamole": 19.99,
			"salsa":     5.99,
		},
	}

	// Parse a JSONPath and select from the input.
	p := jsonpath.MustParse(`$.apps["salsa", "guacamole"]`)
	nodes := p.SelectLocated(menu)

	// Show the nodes.
	fmt.Println("Nodes:")
	for n := range nodes.Nodes() {
		fmt.Printf("  %v\n", n)
	}

	// Show the paths.
	fmt.Println("\nPaths:")
	for p := range nodes.Paths() {
		fmt.Printf("  %v\n", p)
	}

}
Output:

Nodes:
  5.99
  19.99

Paths:
  $['apps']['salsa']
  $['apps']['guacamole']

func (LocatedNodeList) All added in v0.3.0

func (list LocatedNodeList) All() iter.Seq[*spec.LocatedNode]

All returns an iterator over all the nodes in list.

Range over list itself to get indexes and node values.

func (LocatedNodeList) Clone added in v0.3.0

func (list LocatedNodeList) Clone() LocatedNodeList

Clone returns a shallow copy of list.

Example
package main

import (
	"fmt"

	"github.com/theory/jsonpath"
)

func main() {
	// Load some JSON.
	items := []any{1, 2, 3, 4, 5}

	// Parse a JSONPath and select from the input.
	p := jsonpath.MustParse("$[2, 0, 1, 0, 1]")
	nodes := p.SelectLocated(items)

	// Clone the selected nodes then deduplicate.
	orig := nodes.Clone()
	nodes = nodes.Deduplicate()

	// Cloned nodes have the original count.
	fmt.Printf("Unique Count:   %v\nOriginal Count: %v\n", len(nodes), len(orig))

}
Output:

Unique Count:   3
Original Count: 5

func (LocatedNodeList) Deduplicate added in v0.3.0

func (list LocatedNodeList) Deduplicate() LocatedNodeList

Deduplicate deduplicates the nodes in list based on their [NormalizedPath] values, modifying the contents of list. It returns the modified list, which may have a shorter length, and zeroes the elements between the new length and the original length.

Example
package main

import (
	"fmt"

	"github.com/theory/jsonpath"
)

func main() {
	// Load some JSON.
	pallet := map[string]any{"colors": []any{"red", "blue"}}

	// Parse a JSONPath and select from the input.
	p := jsonpath.MustParse("$.colors[0, 1, 1, 0]")
	nodes := p.SelectLocated(pallet)
	fmt.Printf("Items: %v\n", len(nodes))

	// Deduplicate
	nodes = nodes.Deduplicate()
	fmt.Printf("Items: %v\n", len(nodes))

}
Output:

Items: 4
Items: 2

func (LocatedNodeList) Nodes added in v0.3.0

func (list LocatedNodeList) Nodes() iter.Seq[any]

Nodes returns an iterator over all the nodes in list. This is the same data as returned by Path.Select.

func (LocatedNodeList) Paths added in v0.3.0

func (list LocatedNodeList) Paths() iter.Seq[spec.NormalizedPath]

Paths returns an iterator over all the [NormalizedPath] values in list.

func (LocatedNodeList) Sort added in v0.3.0

func (list LocatedNodeList) Sort()

Sort sorts list by the [NormalizedPath] of each node.

Example
package main

import (
	"fmt"

	"github.com/theory/jsonpath"
)

func main() {
	// Load some JSON.
	pallet := map[string]any{"colors": []any{"red", "blue", "green"}}

	// Parse a JSONPath and select from the input.
	p := jsonpath.MustParse("$.colors[2, 0, 1]")
	nodes := p.SelectLocated(pallet)

	// Show selected.
	fmt.Println("Selected:")
	for _, node := range nodes {
		fmt.Printf("  %v: %v\n", node.Path, node.Node)
	}

	// Sort by normalized paths and show selected again.
	nodes.Sort()
	fmt.Println("\nSorted:")
	for _, node := range nodes {
		fmt.Printf("  %v: %v\n", node.Path, node.Node)
	}

}
Output:

Selected:
  $['colors'][2]: green
  $['colors'][0]: red
  $['colors'][1]: blue

Sorted:
  $['colors'][0]: red
  $['colors'][1]: blue
  $['colors'][2]: green

type NodeList added in v0.3.0

type NodeList []any

NodeList is a list of nodes selected by a JSONPath query. Each node represents a single JSON value selected from the JSON query argument. Returned by Path.Select.

func (NodeList) All added in v0.3.0

func (list NodeList) All() iter.Seq[any]

All returns an iterator over all the nodes in list.

Range over list itself to get indexes as well as values.

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 registry.Registry, which may contain function extensions.

Example

Use WithRegistry to create a Parser that uses 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},
	}
	nodes := path.Select(input)
	fmt.Printf("%v\n", nodes)
}

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

	if !args[0].ConvertsTo(spec.FuncNodes) {
		return errors.New("cannot convert argument to nodes")
	}

	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.PathValue) spec.PathValue {
	nodes := spec.NodesFrom(jv[0])
	if len(nodes) == 0 {
		return nil
	}
	return spec.Value(nodes[0])
}
Output:

[[6 7 8 9]]

type Parser

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

Parser parses JSONPath strings into Path values.

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

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 JSONPath 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 JSONPath 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) MarshalBinary added in v0.10.0

func (p *Path) MarshalBinary() ([]byte, error)

MarshalBinary encodes p into UTF-8-encoded bytes and returns the result. Implements encoding.BinaryMarshaler.

func (*Path) MarshalText added in v0.10.0

func (p *Path) MarshalText() ([]byte, error)

MarshalText encodes p into UTF-8-encoded text and returns the result. Implements encoding.TextMarshaler.

func (*Path) Query

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

Query returns p's root spec.PathQuery.

func (*Path) Select

func (p *Path) Select(input any) NodeList

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

Example
package main

import (
	"fmt"

	"github.com/theory/jsonpath"
)

func main() {
	// Load some JSON.
	menu := map[string]any{
		"apps": map[string]any{
			"guacamole": 19.99,
			"salsa":     5.99,
		},
	}

	// Parse a JSONPath and select from the input.
	p := jsonpath.MustParse("$.apps.*")
	nodes := p.Select(menu)

	// Show the selected values.
	for node := range nodes.All() {
		fmt.Printf("%v\n", node)
	}
}
Output:

19.99
5.99

func (*Path) SelectLocated added in v0.3.0

func (p *Path) SelectLocated(input any) LocatedNodeList

SelectLocated returns the nodes that JSONPath query p selects from input as spec.LocatedNode values that pair the nodes with the normalized paths that identify them. Unless you have a specific need for the unique spec.NormalizedPath for each value, you probably want to use Path.Select.

Example
package main

import (
	"fmt"

	"github.com/theory/jsonpath"
)

func main() {
	// Load some JSON.
	menu := map[string]any{
		"apps": map[string]any{
			"guacamole": 19.99,
			"salsa":     5.99,
		},
	}

	// Parse a JSONPath and select from the input.
	p := jsonpath.MustParse("$.apps.*")
	nodes := p.SelectLocated(menu)

	// Show the selected nodes.
	for node := range nodes.All() {
		fmt.Printf("%v: %v\n", node.Path, node.Node)
	}

}
Output:

$['apps']['guacamole']: 19.99
$['apps']['salsa']: 5.99

func (*Path) String

func (p *Path) String() string

String returns a string representation of p.

func (*Path) UnmarshalBinary added in v0.10.0

func (p *Path) UnmarshalBinary(data []byte) error

UnmarshalBinary decodes UTF-8-encoded bytes into p. Implements encoding.BinaryUnmarshaler.

func (*Path) UnmarshalText added in v0.10.0

func (p *Path) UnmarshalText(data []byte) error

UnmarshalText decodes UTF-8-encoded text into p. Implements encoding.TextUnmarshaler.

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 extension registry.
Package registry provides a RFC 9535 JSONPath function extension registry.
Package spec provides the [RFC 9535 JSONPath] [AST] and execution for github.com/theory/jsonpath.
Package spec provides the [RFC 9535 JSONPath] [AST] and execution for github.com/theory/jsonpath.

Jump to

Keyboard shortcuts

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