jsonpath

package module
v0.1.17 Latest Latest
Warning

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

Go to latest
Published: Jul 13, 2026 License: MIT Imports: 17 Imported by: 0

README

JSONPath

Go Module RFC 9535

A Go implementation of RFC 9535 JSONPath with compliance-suite coverage, located results, and typed filter function extensions

Features

  • RFC 9535 JSONPath: Parses and evaluates standard JSONPath expressions against Go JSON values.
  • Compiled paths: Reuse Path values safely across documents and goroutines.
  • Go-native selection: Query decoded any, []any, map[string]any, strings, numbers, booleans, and nil.
  • JSON helpers: Query []byte and io.Reader inputs with QueryJSON* convenience functions.
  • Located results: Return immutable NormalizedPath values alongside matched nodes.
  • Typed function extensions: Register custom filter functions with explicit Value, Logical, and Nodes runtime values.
  • Structured diagnostics: Inspect parse failures with errors.Is and errors.As.

Installation

go get github.com/agentable/jsonpath

Requires Go 1.26.5+.

Quick Start

package main

import (
	"fmt"
	"log"

	"github.com/agentable/jsonpath"
)

func main() {
	path, err := jsonpath.Parse("$.store.book[*].author")
	if err != nil {
		log.Fatal(err)
	}

	data := map[string]any{
		"store": map[string]any{
			"book": []any{
				map[string]any{"author": "Nigel Rees"},
				map[string]any{"author": "Evelyn Waugh"},
			},
		},
	}

	for author := range path.Select(data).All() {
		fmt.Println(author)
	}
}

API Overview

Task API
Compile expressions Parse, MustParse, Valid, NewParser
Inspect errors ParseError, ErrPathParse, ErrFunction, ErrUnmarshal, ErrInvalidPath
Query decoded values Path.Select, Path.SelectLocated
Query JSON bytes/readers QueryJSON, QueryJSONRead, QueryJSONLocated, QueryJSONReadLocated
Iterate values NodeList.All, LocatedNodeList.All, LocatedNodeList.Values, LocatedNodeList.Paths
Work with paths NormalizedPath.String, NormalizedPath.Pointer, NormalizedPath.Elements, NormalizedPath.Append
Extend filters WithFunctions, NewValueFunction, NewLogicalFunction, NewNodesFunction

See pkg.go.dev/github.com/agentable/jsonpath for complete package documentation.

Valid(expr) checks whether Parse(expr) succeeds with the default built-in functions; use NewParser and Parser.Parse for expressions that rely on custom functions. QueryJSON* helpers preserve JSON numbers as encoding/json.Number; invalid Path values fail with ErrInvalidPath before reading or decoding. Callers that need another number representation, decoder policy, or streaming behavior should decode outside this package and then call Path.Select or Path.SelectLocated.

Filter comparison preserves the exact magnitude of Go integer types and the exact decimal value of encoding/json.Number. Finite float32 and float64 values supplied to Select compare by their exact binary value. Invalid json.Number text, NaN, and infinities are outside the decoded-JSON numeric domain and are not comparable.

Core Concepts

Compiled Paths

Compile once, select many times:

path := jsonpath.MustParse("$['store']['book'][*]['title']")
titles := path.Select(data)

Path.String() and Path.MarshalText() return a stable, canonical JSONPath string for the compiled query. The zero Path value is invalid; String() returns an empty string and MarshalText() returns ErrInvalidPath.

Located Results

Use located queries when you need both the value and its RFC 9535 normalized path.

src := []byte(`{"store":{"book":[{"title":"Moby Dick"},{"title":"1984"}]}}`)
path := jsonpath.MustParse("$.store.book[*].title")

located, err := jsonpath.QueryJSONLocated(src, path)
if err != nil {
	log.Fatal(err)
}

for node := range located.All() {
	fmt.Printf("%s -> %s = %v\n", node.Path, node.Path.Pointer(), node.Value)
}

NormalizedPath is immutable. NewNormalizedPath and Append reject nil elements and negative indexes with ErrInvalidPath. Read elements through Element or Elements. LocatedNodeList is a nodelist in query order, not a set. Use Values and Paths to iterate just values or paths. Sort and Deduplicate are caller opt-in operations; Deduplicate is path-based, keeps the first node for each path, and ignores value equality.

path, err := jsonpath.NewNormalizedPath(
	jsonpath.NameElement("store"),
	jsonpath.NameElement("book"),
	jsonpath.IndexElement(0),
)
if err != nil {
	log.Fatal(err)
}

fmt.Println(path.String())  // $['store']['book'][0]
fmt.Println(path.Pointer()) // /store/book/0
Parse Diagnostics

Parse failures satisfy ErrPathParse. Function lookup, validation, and registration failures also satisfy ErrFunction. JSON decode failures from QueryJSON* wrap ErrUnmarshal, and invalid compiled paths return ErrInvalidPath.

_, err := jsonpath.Parse(" $.store")

var parseErr *jsonpath.ParseError
if errors.As(err, &parseErr) {
	fmt.Println(errors.Is(err, jsonpath.ErrPathParse))
	fmt.Println(parseErr.Offset)
	fmt.Println(parseErr.Reason)
}
Function Extensions

Register custom filter functions with NewParser(jsonpath.WithFunctions(...)). Invalid function options fail at parser construction with ErrFunction. Runtime arguments and return values are typed:

  • jsonpath.Value: one JSON value or NoValue
  • jsonpath.Logical: boolean test result
  • jsonpath.Nodes: node list result

Functions have one immutable name, fixed parameter list, result category, and callback. The parser validates argument conversions from that signature; callbacks receive only typed runtime values. Return NoValue, Logical(false), or an empty Nodes value for runtime absence. FuncLogical parameters accept the same comparisons, test expressions, negation, conjunction, disjunction, and parentheses used by filters.

hasPrefix := jsonpath.NewLogicalFunction(
	"has_prefix",
	[]jsonpath.FuncType{jsonpath.FuncValue, jsonpath.FuncValue},
	func(args []jsonpath.FunctionValue) jsonpath.Logical {
		value, ok := args[0].(jsonpath.Value)
		if !ok || value.IsNothing() {
			return jsonpath.Logical(false)
		}
		prefix, ok := args[1].(jsonpath.Value)
		if !ok || prefix.IsNothing() {
			return jsonpath.Logical(false)
		}

		s, ok := value.Any().(string)
		p, ok2 := prefix.Any().(string)
		return jsonpath.Logical(ok && ok2 && strings.HasPrefix(s, p))
	},
)

parser, err := jsonpath.NewParser(jsonpath.WithFunctions(hasPrefix))
if err != nil {
	log.Fatal(err)
}
path, err := parser.Parse(`$[?has_prefix(@.sku, "book-")]`)
if err != nil {
	log.Fatal(err)
}

Use NewValueFunction, NewLogicalFunction, or NewNodesFunction according to the result category. WithFunctions rejects zero-value definitions, nil callbacks, invalid names or parameter types, duplicates, and built-in collisions when NewParser is called. Function names use RFC function-name grammar: a lowercase ASCII letter followed by lowercase ASCII letters, digits, or underscores.

Performance

The package includes benchmarks for selector execution, located queries, filter evaluation, normalized-path construction, JSON decoding, and PGO workflows.

task bench
go test -run '^$' -bench 'Benchmark(Select_|SelectLocated_|QueryJSON|ExtendPath|ParserParse)' -benchmem -count=10 .
task pgo:generate
task bench-pgo

Compare results only when the machine, Go toolchain, PGO mode, benchmark regex, and repetition count are identical.

Regenerate default.pgo only when representative query hot paths change materially.

Development

task fmt        # format code
task lint       # tidy-lint + golangci-lint
task test       # go test -race ./...
task vet        # go vet ./...
task verify     # deps + fmt + vet + lint + test + vuln

For development conventions and repository workflow, see CLAUDE.md.

Contributing

Run task fmt, task lint, and task test before submitting changes.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Documentation

Overview

Package jsonpath implements RFC 9535 JSONPath queries for Go values.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrPathParse is returned when a JSONPath expression cannot be parsed.
	ErrPathParse = errors.New("jsonpath: parse error")
	// ErrFunction is returned for function registration or expression failures.
	ErrFunction = errors.New("jsonpath: function error")
	// ErrUnmarshal is returned when JSON unmarshaling fails in QueryJSON functions.
	ErrUnmarshal = errors.New("jsonpath: unmarshal error")
	// ErrInvalidPath is returned for an invalid compiled or normalized path.
	ErrInvalidPath = errors.New("jsonpath: invalid path")
)

Functions

func Valid

func Valid(expr string) bool

Valid reports whether expr is a syntactically valid JSONPath expression.

Types

type FuncType

type FuncType uint8

FuncType describes a function extension's parameter or result type as defined by RFC 9535 §2.4.1.

const (
	// FuncLogical indicates a logical (bool) parameter or result.
	FuncLogical FuncType = iota
	// FuncValue indicates a single-JSON-value parameter or result.
	FuncValue
	// FuncNodes indicates a node-list parameter or result.
	FuncNodes
)

type Function

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

Function is an immutable extension function definition created by NewValueFunction, NewLogicalFunction, or NewNodesFunction. Callbacks must be safe for concurrent use when the configured Parser is used concurrently.

func NewLogicalFunction added in v0.1.17

func NewLogicalFunction(name string, params []FuncType, call func([]FunctionValue) Logical) Function

NewLogicalFunction defines a function that returns a logical value.

func NewNodesFunction added in v0.1.17

func NewNodesFunction(name string, params []FuncType, call func([]FunctionValue) Nodes) Function

NewNodesFunction defines a function that returns a node list.

func NewValueFunction added in v0.1.17

func NewValueFunction(name string, params []FuncType, call func([]FunctionValue) Value) Function

NewValueFunction defines a function that returns a JSON value.

type FunctionValue added in v0.1.16

type FunctionValue = ast.FunctionValue

FunctionValue is one of the RFC 9535 function runtime value types.

type IndexElement

type IndexElement int

IndexElement is an array index in a normalized path.

type LocatedNode

type LocatedNode struct {
	Value any            // Value is the selected JSON value.
	Path  NormalizedPath // Path is the normalized path to Value.
}

LocatedNode pairs a value with the NormalizedPath for its location within a JSON query argument.

type LocatedNodeList

type LocatedNodeList []LocatedNode

LocatedNodeList is a list of nodes selected by a JSONPath query, along with their NormalizedPath locations.

func QueryJSONLocated

func QueryJSONLocated(src []byte, path Path) (LocatedNodeList, error)

QueryJSONLocated unmarshals src, preserving JSON numbers as encoding/json.Number, and evaluates path against it, returning matched nodes together with their normalized paths.

Example
package main

import (
	"fmt"
	"log"

	"github.com/agentable/jsonpath"
)

func main() {
	src := []byte(`{"store":{"book":[{"title":"Moby Dick"},{"title":"1984"}]}}`)
	path := jsonpath.MustParse("$.store.book[*].title")

	located, err := jsonpath.QueryJSONLocated(src, path)
	if err != nil {
		log.Fatal(err)
	}

	for node := range located.All() {
		fmt.Printf("%s = %v\n", node.Path, node.Value)
	}

}
Output:
$['store']['book'][0]['title'] = Moby Dick
$['store']['book'][1]['title'] = 1984

func QueryJSONReadLocated added in v0.1.4

func QueryJSONReadLocated(r io.Reader, path Path) (LocatedNodeList, error)

QueryJSONReadLocated unmarshals JSON from r, preserving numbers as encoding/json.Number, and evaluates path, returning matched nodes together with their normalized paths.

func (LocatedNodeList) All

All returns an iterator over all the located nodes in list.

func (LocatedNodeList) Deduplicate

func (l 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.

func (LocatedNodeList) Paths

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

func (LocatedNodeList) Sort

func (l LocatedNodeList) Sort()

Sort sorts list by the NormalizedPath of each node.

func (LocatedNodeList) Values

func (l LocatedNodeList) Values() iter.Seq[any]

Values returns an iterator over all the node values in list.

type Logical added in v0.1.16

type Logical = ast.TypedLogical

Logical is a logical value passed to or returned from a JSONPath function.

type NameElement

type NameElement string

NameElement is a string key in a normalized path.

type NodeList

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.

func QueryJSON

func QueryJSON(src []byte, path Path) (NodeList, error)

QueryJSON unmarshals src, preserving JSON numbers as encoding/json.Number, and evaluates path against it.

func QueryJSONRead added in v0.1.4

func QueryJSONRead(r io.Reader, path Path) (NodeList, error)

QueryJSONRead unmarshals JSON from r, preserving numbers as encoding/json.Number, and evaluates path.

func (NodeList) All

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

All returns an iterator over all the nodes in list.

type Nodes added in v0.1.16

type Nodes = ast.TypedNodes

Nodes is a node-list value passed to or returned from a JSONPath function.

type NormalizedPath

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

NormalizedPath is an immutable sequence of Name/Index selectors per RFC 9535 §2.7. The zero value is the root path.

func NewNormalizedPath added in v0.1.16

func NewNormalizedPath(elements ...PathElement) (NormalizedPath, error)

NewNormalizedPath returns a normalized path from validated elements.

func (NormalizedPath) Append added in v0.1.16

func (p NormalizedPath) Append(elem PathElement) (NormalizedPath, error)

Append returns a path with a validated element appended.

func (NormalizedPath) Compare

func (p NormalizedPath) Compare(q NormalizedPath) int

Compare compares p to q and returns -1, 0, or 1. Indexes are always considered less than names.

func (NormalizedPath) Element added in v0.1.16

func (p NormalizedPath) Element(i int) PathElement

Element returns the i'th path element.

func (NormalizedPath) Elements added in v0.1.16

func (p NormalizedPath) Elements() []PathElement

Elements returns a copy of p's path elements.

func (NormalizedPath) Equal added in v0.1.16

func (p NormalizedPath) Equal(q NormalizedPath) bool

Equal reports whether p and q contain the same path elements.

func (NormalizedPath) Len added in v0.1.16

func (p NormalizedPath) Len() int

Len returns the number of path elements.

func (NormalizedPath) MarshalText

func (p NormalizedPath) MarshalText() ([]byte, error)

MarshalText marshals p into its normalized path string. Implements encoding.TextMarshaler.

func (NormalizedPath) Pointer

func (p NormalizedPath) Pointer() string

Pointer returns an RFC 6901 JSON Pointer string, e.g. /a/0.

func (NormalizedPath) String

func (p NormalizedPath) String() string

String returns the normalized path string, e.g. $['a'][0].

type Option

type Option func(*parserOptions)

Option configures a Parser.

func WithFunctions

func WithFunctions(fns ...Function) Option

WithFunctions registers additional filter functions beyond the RFC 9535 built-ins. Function names must follow RFC function-name grammar: a lowercase ASCII letter followed by lowercase ASCII letters, digits, or underscores. Names must not duplicate another registered function or override a built-in.

Example
package main

import (
	"fmt"
	"log"
	"strings"

	"github.com/agentable/jsonpath"
)

func hasPrefix(args []jsonpath.FunctionValue) jsonpath.Logical {
	if len(args) != 2 {
		return jsonpath.Logical(false)
	}

	value, ok := args[0].(jsonpath.Value)
	if !ok || value.IsNothing() {
		return jsonpath.Logical(false)
	}
	prefix, ok := args[1].(jsonpath.Value)
	if !ok || prefix.IsNothing() {
		return jsonpath.Logical(false)
	}

	s, ok := value.Any().(string)
	if !ok {
		return jsonpath.Logical(false)
	}
	p, ok := prefix.Any().(string)
	return jsonpath.Logical(ok && strings.HasPrefix(s, p))
}

func main() {
	fn := jsonpath.NewLogicalFunction(
		"has_prefix",
		[]jsonpath.FuncType{jsonpath.FuncValue, jsonpath.FuncValue},
		hasPrefix,
	)
	parser, err := jsonpath.NewParser(jsonpath.WithFunctions(fn))
	if err != nil {
		log.Fatal(err)
	}
	path, err := parser.Parse(`$[?has_prefix(@.sku, "book-")]`)
	if err != nil {
		log.Fatal(err)
	}

	items := []any{
		map[string]any{"sku": "book-1"},
		map[string]any{"sku": "pen-1"},
	}

	for item := range path.Select(items).All() {
		fmt.Println(item.(map[string]any)["sku"])
	}

}
Output:
book-1

type ParseError added in v0.1.16

type ParseError struct {
	// Offset is the byte offset in the original expression where parsing failed.
	Offset int
	// Reason is a short human-readable reason for the failure.
	Reason string
	// Snippet is a short source excerpt around Offset.
	Snippet string
	// Cause is the underlying lexer, parser, or function validation error.
	Cause error
}

ParseError describes a JSONPath parse failure in a program-readable form.

Example
package main

import (
	"errors"
	"fmt"

	"github.com/agentable/jsonpath"
)

func main() {
	_, err := jsonpath.Parse(" $.store")

	var parseErr *jsonpath.ParseError
	if errors.As(err, &parseErr) {
		fmt.Println(errors.Is(err, jsonpath.ErrPathParse))
		fmt.Println(parseErr.Offset)
		fmt.Println(parseErr.Reason)
	}

}
Output:
true
0
leading whitespace not allowed

func (*ParseError) Error added in v0.1.16

func (e *ParseError) Error() string

Error returns a human-readable parse error message.

func (*ParseError) Unwrap added in v0.1.16

func (e *ParseError) Unwrap() error

Unwrap returns the underlying parse failure cause.

type Parser

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

Parser parses JSONPath expressions into Path values, optionally configured with extension functions.

func NewParser

func NewParser(opts ...Option) (*Parser, error)

NewParser creates a new Parser configured by opts. Invalid options return an error satisfying ErrFunction.

func (*Parser) MustParse

func (p *Parser) MustParse(expr string) Path

MustParse compiles a JSONPath expression. Panics on failure.

func (*Parser) Parse

func (p *Parser) Parse(expr string) (Path, error)

Parse compiles a JSONPath expression. Returns ErrPathParse on failure.

type Path

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

Path is a compiled RFC 9535 JSONPath query. Safe for concurrent use.

func MustParse

func MustParse(expr string) Path

MustParse compiles a JSONPath expression. Panics on failure.

func Parse

func Parse(expr string) (Path, error)

Parse compiles a JSONPath expression. Returns ErrPathParse on failure.

func (Path) MarshalText

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

MarshalText implements encoding.TextMarshaler.

func (Path) Select

func (p Path) Select(input any) NodeList

Select returns all nodes matched by p in input. input must be a decoded JSON value (any / []any / map[string]any / primitive).

Example
package main

import (
	"fmt"
	"log"

	"github.com/agentable/jsonpath"
)

func main() {
	path, err := jsonpath.Parse("$.store.book[*].author")
	if err != nil {
		log.Fatal(err)
	}

	data := map[string]any{
		"store": map[string]any{
			"book": []any{
				map[string]any{"author": "Nigel Rees"},
				map[string]any{"author": "Evelyn Waugh"},
			},
		},
	}

	for author := range path.Select(data).All() {
		fmt.Println(author)
	}

}
Output:
Nigel Rees
Evelyn Waugh

func (Path) SelectLocated

func (p Path) SelectLocated(input any) LocatedNodeList

SelectLocated returns matched nodes paired with their normalized paths.

func (Path) String

func (p Path) String() string

String returns the canonical string representation of p.

func (*Path) UnmarshalText

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

UnmarshalText implements encoding.TextUnmarshaler.

type PathElement

type PathElement interface {
	// contains filtered or unexported methods
}

PathElement is either a Name (string key) or an Index (array index) in a normalized path. Implemented by NameElement and IndexElement.

type Value added in v0.1.16

type Value = ast.TypedValue

Value is a JSON value passed to or returned from a JSONPath function.

func NewValue added in v0.1.16

func NewValue(value any) Value

NewValue returns a present JSON value for function execution.

func NoValue added in v0.1.16

func NoValue() Value

NoValue returns an absent value for function execution.

Directories

Path Synopsis
internal
ast
Package ast defines the JSONPath abstract syntax tree.
Package ast defines the JSONPath abstract syntax tree.
functions
Package functions provides the RFC 9535 §2.4 built-in function implementations for JSONPath filter expressions.
Package functions provides the RFC 9535 §2.4 built-in function implementations for JSONPath filter expressions.
lexer
Package lexer provides a hand-written, zero-copy lexer for RFC 9535 JSONPath expressions.
Package lexer provides a hand-written, zero-copy lexer for RFC 9535 JSONPath expressions.
parser
Package parser provides a recursive descent parser for RFC 9535 JSONPath expressions.
Package parser provides a recursive descent parser for RFC 9535 JSONPath expressions.

Jump to

Keyboard shortcuts

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