Skip to content

Commit dbb48f6

Browse files
committed
Start adding spec examples
1 parent ba21c68 commit dbb48f6

File tree

5 files changed

+246
-18
lines changed

5 files changed

+246
-18
lines changed

README.md

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,21 +33,6 @@ A brief overview of [RFC 9535 JSONPath] syntax:
3333
| `?<logical-expr>` | filter selector: selects particular children using a logical expression |
3434
| `length(@.foo)` | function extension: invokes a function in a filter expression |
3535

36-
## Package Stability
37-
38-
The root `jsonpath` package is stable and ready for use. These are the main
39-
interfaces to the package.
40-
41-
The `registry` package is also stable, but exposes data types from the `spec`
42-
package that are still in flux. Argument data types may still change.
43-
44-
The `parser` package interface is also stable, but in general should not be
45-
used directly.
46-
47-
The `spec` package remains under active development, mainly refactoring,
48-
reorganizing, renaming, and documenting. Its interface therefore is not stable
49-
and should not be used for production purposes.
50-
5136
## Copyright
5237

5338
Copyright © 2024-2025 David E. Wheeler

spec/op.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@ func (ce *ComparisonExpr) writeTo(buf *strings.Builder) {
5454
ce.Right.writeTo(buf)
5555
}
5656

57+
// String returns the string representation of ce.
58+
func (ce *ComparisonExpr) String() string {
59+
var buf strings.Builder
60+
ce.writeTo(&buf)
61+
return buf.String()
62+
}
63+
5764
// testFilter uses ce.Op to compare the values returned by ce.Left and
5865
// ce.Right relative to current and root.
5966
func (ce *ComparisonExpr) testFilter(current, root any) bool {

spec/selector.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
// Package spec provides object definitions and execution for RFC 9535
2-
// JSONPath query expressions. It remains under active development therefore
3-
// should generally not be used directly except for experimental purposes.
41
package spec
52

63
import (

spec/spec.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Package spec provides RFC 9535 JSONPath query AST and execution. It will
2+
// mainly be of interest to those wishing to implement their own parsers, to
3+
// convert from other JSON path-type languages, and to implement functions for
4+
// [github.com/theory/jsonpath/registry].
5+
package spec

spec/spec_example_test.go

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
package spec_test
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"log"
7+
8+
"github.com/theory/jsonpath"
9+
"github.com/theory/jsonpath/spec"
10+
)
11+
12+
// Select all the authors of the books in a bookstore object.
13+
func Example() {
14+
// Construct a jsonpath query.
15+
p := jsonpath.New(spec.Query(
16+
true,
17+
spec.Child(spec.Name("store")),
18+
spec.Child(spec.Name("book")),
19+
spec.Child(spec.Wildcard),
20+
spec.Child(spec.Name("author")),
21+
))
22+
23+
// Select values from unmarshaled JSON input.
24+
store := bookstore()
25+
nodes := p.Select(store)
26+
27+
// Show the selected values.
28+
items, err := json.Marshal(nodes)
29+
if err != nil {
30+
log.Fatal(err)
31+
}
32+
fmt.Printf("%s\n", items)
33+
34+
// Output: ["Nigel Rees","Evelyn Waugh","Herman Melville","J. R. R. Tolkien"]
35+
}
36+
37+
// Construct a couple of different path queries.
38+
func ExamplePathQuery() {
39+
// Create a query and its segments.
40+
q := spec.Query(
41+
true,
42+
spec.Child(spec.Name("store")),
43+
spec.Child(spec.Name("book")),
44+
spec.Child(spec.Wildcard),
45+
spec.Child(spec.Name("author")),
46+
)
47+
fmt.Printf("%v\n", q)
48+
49+
// Create a query with multi-selector segments.
50+
q = spec.Query(
51+
true,
52+
spec.Child(spec.Name("profile")),
53+
spec.Descendant(spec.Name("email"), spec.Name("phone")),
54+
)
55+
fmt.Printf("%v\n", q)
56+
57+
// Output:
58+
// $["store"]["book"][*]["author"]
59+
// $["profile"]..["email","phone"]
60+
}
61+
62+
// Create a child segment that selects the name "hi", th index 2, or slice
63+
// 1-3.
64+
func ExampleSegment_child() {
65+
child := spec.Child(
66+
spec.Name("hi"),
67+
spec.Index(2),
68+
spec.Slice(1, 3, 1),
69+
)
70+
fmt.Printf("%v\n", child)
71+
// Output: ["hi",2,1:3]
72+
}
73+
74+
// Create a descendant segment that selects the name "email" or array index
75+
// zero from a node or any of its descendant nodes.
76+
func ExampleSegment_descendant() {
77+
child := spec.Descendant(
78+
spec.Name("email"),
79+
spec.Index(0),
80+
)
81+
fmt.Printf("%v\n", child)
82+
// Output: ..["email",0]
83+
}
84+
85+
// Create a few types of slice selectors.
86+
func ExampleSliceSelector() {
87+
// Select all values in a slice.
88+
for _, sliceSelector := range []any{
89+
spec.Slice(), // full slice
90+
spec.Slice(1, 4), // items 1-3
91+
spec.Slice(nil, 8), // items 0-7
92+
spec.Slice(4, -1, 3), // items 4-last step by 3
93+
spec.Slice(5, 1, -2), // items 5-2, step by -2
94+
95+
} {
96+
fmt.Printf("%v\n", sliceSelector)
97+
}
98+
// Output:
99+
// :
100+
// 1:4
101+
// :8
102+
// 4:-1:3
103+
// 5:1:-2
104+
}
105+
106+
// Create a few types of name selectors.
107+
func ExampleName() {
108+
for _, nameSelector := range []any{
109+
spec.Name("hello"), // ascii
110+
spec.Name(`Charlie "Bird" Parker`), // quoted
111+
spec.Name("އައްސަލާމް ޢަލައިކުމް"), // Unicode
112+
spec.Name("📡🪛🪤"), // emoji
113+
} {
114+
fmt.Printf("%v\n", nameSelector)
115+
}
116+
// Output:
117+
// "hello"
118+
// "Charlie \"Bird\" Parker"
119+
// "އައްސަލާމް ޢަލައިކުމް"
120+
// "📡🪛🪤"
121+
}
122+
123+
// Create a few types of index selectors.
124+
func ExampleIndex() {
125+
for _, indexSelector := range []any{
126+
spec.Index(0), // first item
127+
spec.Index(3), // fourth item
128+
spec.Index(-1), // last item
129+
} {
130+
fmt.Printf("%v\n", indexSelector)
131+
}
132+
// Output:
133+
// 0
134+
// 3
135+
// -1
136+
}
137+
138+
func ExampleWildcard() {
139+
fmt.Printf("%v\n", spec.Wildcard)
140+
// Output: *
141+
}
142+
143+
// Create a filter selector that selects nodes with a descendant that contains
144+
// an object field named "x".
145+
func ExampleFilterSelector() {
146+
f := spec.Filter(spec.And(
147+
spec.Existence(
148+
spec.Query(false, spec.Descendant(spec.Name("x"))),
149+
),
150+
))
151+
fmt.Printf("%v\n", f)
152+
// Output: ?@..["x"]
153+
}
154+
155+
// Create a comparison expression that compares a literal value to a path
156+
// expression.
157+
func ExampleComparisonExpr() {
158+
cmp := spec.Comparison(
159+
spec.Literal(42),
160+
spec.EqualTo,
161+
spec.SingularQuery(false, spec.Name("age")),
162+
)
163+
fmt.Printf("%v\n", cmp)
164+
// Output: 42 == @["age"]
165+
}
166+
167+
// Create an existence expression as a filter expression.
168+
func ExampleExistExpr() {
169+
filter := spec.Filter(spec.And(
170+
spec.Existence(
171+
spec.Query(false, spec.Child(spec.Name("x"))),
172+
),
173+
))
174+
fmt.Printf("%v\n", filter)
175+
// Output: ?@["x"]
176+
}
177+
178+
// Create a nonexistence expression as a filter expression.
179+
func ExampleNonExistExpr() {
180+
filter := spec.Filter(spec.And(
181+
spec.Nonexistence(
182+
spec.Query(false, spec.Child(spec.Name("x"))),
183+
),
184+
))
185+
fmt.Printf("%v\n", filter)
186+
// Output: ?!@["x"]
187+
}
188+
189+
// bookstore returns an unmarshaled JSON object.
190+
func bookstore() any {
191+
src := []byte(`{
192+
"store": {
193+
"book": [
194+
{
195+
"category": "reference",
196+
"author": "Nigel Rees",
197+
"title": "Sayings of the Century",
198+
"price": 8.95
199+
},
200+
{
201+
"category": "fiction",
202+
"author": "Evelyn Waugh",
203+
"title": "Sword of Honour",
204+
"price": 12.99
205+
},
206+
{
207+
"category": "fiction",
208+
"author": "Herman Melville",
209+
"title": "Moby Dick",
210+
"isbn": "0-553-21311-3",
211+
"price": 8.99
212+
},
213+
{
214+
"category": "fiction",
215+
"author": "J. R. R. Tolkien",
216+
"title": "The Lord of the Rings",
217+
"isbn": "0-395-19395-8",
218+
"price": 22.99
219+
}
220+
],
221+
"bicycle": {
222+
"color": "red",
223+
"price": 399
224+
}
225+
}
226+
}`)
227+
228+
// Parse the JSON.
229+
var value any
230+
if err := json.Unmarshal(src, &value); err != nil {
231+
log.Fatal(err)
232+
}
233+
return value
234+
}

0 commit comments

Comments
 (0)