Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
uses: actions/setup-go@v5
with: { go-version: "${{ matrix.go }}", check-latest: true }
- name: Run Tests
run: make test
run: make test-all
- name: Setup TinyGo
uses: acifani/setup-tinygo@v2
with: { tinygo-version: 0.37.0 }
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,6 @@ pub/

# Temp files
*.tmp

# Generated files.
/compare/regression_suite.yaml
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,19 @@ All notable changes to this project will be documented in this file. It uses the

## [v0.10.1] — Unreleased

### 🐞 Bug Fixes

* Allow `true`, `false`, and `null` to be used as selectors, e.g., `$.true`.

### 📔 Notes

* Upgraded to `golangci-lint` v2.3.1.
* Fixed test name scoping issues with testify objects.
* Upgraded the [compliance test suite], now with tests using `true`,
`false`, and `null` to be used as selectors.

[v0.10.1]: https://github.com/theory/jsonpath/compare/v0.10.0...v0.10.1
[compliance test suite]: https://github.com/jsonpath-standard/jsonpath-compliance-test-suite

## [v0.10.0] — 2025-07-11

Expand Down
9 changes: 9 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ cover: $(shell find . -name \*.go)
GOTOOLCHAIN=local $(GO) test -v -coverprofile=cover.out -covermode=count ./...
@$(GO) tool cover -html=cover.out

compare/regression_suite.yaml:
curl -so "$@" https://raw.githubusercontent.com/cburgmer/json-path-comparison/refs/heads/master/regression_suite/regression_suite.yaml

test-compare: compare/regression_suite.yaml
GOTOOLCHAIN=local ${GO} test -tags=compare ./compare -count=1

test-all: compare/regression_suite.yaml
GOTOOLCHAIN=local ${GO} test -tags=compare ./... -count=1

.PHONY: lint # Lint the project
lint: .golangci.yaml
@pre-commit run --show-diff-on-failure --color=always --all-files
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ A brief overview of [RFC 9535 JSONPath] syntax:
| `?<logical-expr>` | filter selector: selects particular children using a logical expression |
| `length(@.foo)` | function extension: invokes a function in a filter expression |

## Dependencies

This package has no runtime dependencies, only testing dependencies.

## Copyright

Copyright © 2024-2025 David E. Wheeler
Expand Down
98 changes: 98 additions & 0 deletions compare/compare_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
//go:build compare

// Package compare tests theory/jsonpath against the [json-path-comparison]
// project's regression suite. It requires the file regression_suite.yaml to
// be in this directory. The test only runs with the "compare" tag. Use make
// for the easiest way to download regression_suite.yaml and run the tests:
//
// make test-compare
//
// [json-path-comparison]: https://github.com/cburgmer/json-path-comparison
package compare

import (
"os"
"path/filepath"
"runtime"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/theory/jsonpath"
"gopkg.in/yaml.v3"
)

type Query struct {
ID string `yaml:"id"`
Selector string `yaml:"selector"`
Document any `yaml:"document"`
Consensus any `yaml:"consensus"`
Ordered bool `yaml:"ordered"`
}

func file(t *testing.T) string {
t.Helper()
_, fn, _, ok := runtime.Caller(0)
assert.True(t, ok)
return filepath.Clean(filepath.Join(
filepath.Dir(fn),
"regression_suite.yaml",
))
}

func queries(t *testing.T) []Query {
t.Helper()
data, err := os.ReadFile(file(t))
require.NoError(t, err)
var q struct {
Queries []Query `yaml:"queries"`
}
require.NoError(t, yaml.Unmarshal(data, &q))
return q.Queries
}

func TestConsensus(t *testing.T) {
t.Parallel()

skip := map[string]string{
"array_slice_with_step_and_leading_zeros": "leading zeros disallowed integers; see RFC 9535 sections 2.3.3.1, 2.3.4.1",
"dot_notation_with_number_on_object": "leading digits disallowed in shorthand names; see RFC 9535 section 2.5.1.1",
"dot_notation_with_dash": "dash disallowed in shorthand hames; see RFC 9535 section 2.5.1.1",
}

for _, q := range queries(t) {
t.Run(q.ID, func(t *testing.T) {
t.Parallel()

if q.Consensus == "NOT_SUPPORTED" {
t.Skip(q.Consensus)
}

if r, ok := skip[q.ID]; ok {
t.Skip(r)
}

path, err := jsonpath.Parse(q.Selector)
// XXX Why is consensus empty?
if q.Consensus != nil {
require.NoError(t, err)
}
if err != nil {
// XXX Why is there an error? TODOs?
assert.Nil(t, path)
return
}
result := []any(path.Select(q.Document))

switch {
case q.Ordered:
assert.Equal(t, q.Consensus, result)
case q.Consensus == nil:
// XXX What to do here?
// assert.Empty(t, result)
default:
assert.ElementsMatch(t, q.Consensus, result)
}
})
}
}
6 changes: 4 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ module github.com/theory/jsonpath

go 1.23

require github.com/stretchr/testify v1.10.0
require (
github.com/stretchr/testify v1.10.0
gopkg.in/yaml.v3 v3.0.1
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/pretty v0.3.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
2 changes: 1 addition & 1 deletion parser/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ func (p *parser) parseQuery(root bool) (*spec.PathQuery, error) {
// parsed Selector.
func parseNameOrWildcard(lex *lexer) (spec.Selector, error) {
switch tok := lex.scan(); tok.tok {
case identifier:
case identifier, boolTrue, boolFalse, jsonNull:
return spec.Name(tok.val), nil
case '*':
return spec.Wildcard(), nil
Expand Down
24 changes: 24 additions & 0 deletions parser/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,30 @@ func TestParseSimple(t *testing.T) {
spec.Descendant(spec.Name("xyz")),
),
},
{
test: "true_for_name",
path: `$.true`,
exp: spec.Query(
true,
spec.Child(spec.Name("true")),
),
},
{
test: "false_for_name",
path: `$.false`,
exp: spec.Query(
true,
spec.Child(spec.Name("false")),
),
},
{
test: "null_for_name",
path: `$.null`,
exp: spec.Query(
true,
spec.Child(spec.Name("null")),
),
},
{
test: "empty_string",
path: "",
Expand Down
Loading