+48
-25
bin/jsonpp.ml
+48
-25
bin/jsonpp.ml
···
17
17
| Ok s -> s
18
18
| Error e -> failwith e
19
19
20
+
(* Helper to get indices from either nav or append pointer *)
21
+
let indices_of_result (result : [ `Nav of Jsont_pointer.nav Jsont_pointer.t
22
+
| `Append of Jsont_pointer.append Jsont_pointer.t ]) =
23
+
match result with
24
+
| `Nav p -> Jsont_pointer.indices p
25
+
| `Append p -> Jsont_pointer.indices p
26
+
27
+
(* Helper to convert to string from either nav or append pointer *)
28
+
let to_string_of_result (result : [ `Nav of Jsont_pointer.nav Jsont_pointer.t
29
+
| `Append of Jsont_pointer.append Jsont_pointer.t ]) =
30
+
match result with
31
+
| `Nav p -> Jsont_pointer.to_string p
32
+
| `Append p -> Jsont_pointer.to_string p
33
+
20
34
(* Test: parse pointer and print indices *)
21
35
let test_parse pointer_str =
22
36
try
23
-
let p = Jsont_pointer.of_string pointer_str in
24
-
let indices = Jsont_pointer.indices p in
37
+
let result = Jsont_pointer.of_string pointer_str in
38
+
let indices = indices_of_result result in
25
39
let index_strs = List.map (fun idx ->
26
40
match idx with
27
-
| `Mem s -> Printf.sprintf "Mem:%s" s
28
-
| `Nth n -> Printf.sprintf "Nth:%d" n
29
-
| `End -> "End"
41
+
| Jsont.Path.Mem (s, _) -> Printf.sprintf "Mem:%s" s
42
+
| Jsont.Path.Nth (n, _) -> Printf.sprintf "Nth:%d" n
30
43
) indices in
31
-
Printf.printf "OK: [%s]\n" (String.concat ", " index_strs)
44
+
let suffix = match result with `Nav _ -> "" | `Append _ -> ", /-" in
45
+
Printf.printf "OK: [%s%s]\n" (String.concat ", " index_strs) suffix
32
46
with Jsont.Error e ->
33
47
Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e)
34
48
35
49
(* Test: roundtrip pointer string *)
36
50
let test_roundtrip pointer_str =
37
51
try
38
-
let p = Jsont_pointer.of_string pointer_str in
39
-
let s = Jsont_pointer.to_string p in
52
+
let result = Jsont_pointer.of_string pointer_str in
53
+
let s = to_string_of_result result in
40
54
if s = pointer_str then
41
55
Printf.printf "OK: %s\n" s
42
56
else
···
48
62
let test_eval json_path pointer_str =
49
63
try
50
64
let json = parse_json (read_file json_path) in
51
-
let p = Jsont_pointer.of_string pointer_str in
65
+
let p = Jsont_pointer.of_string_nav pointer_str in
52
66
let result = Jsont_pointer.get p json in
53
67
Printf.printf "OK: %s\n" (json_to_string result)
54
68
with
···
73
87
(* Test: URI fragment roundtrip *)
74
88
let test_uri_fragment pointer_str =
75
89
try
76
-
let p = Jsont_pointer.of_string pointer_str in
77
-
let frag = Jsont_pointer.to_uri_fragment p in
78
-
let p2 = Jsont_pointer.of_uri_fragment frag in
79
-
let s2 = Jsont_pointer.to_string p2 in
90
+
let result = Jsont_pointer.of_string pointer_str in
91
+
let frag = match result with
92
+
| `Nav p -> Jsont_pointer.to_uri_fragment p
93
+
| `Append p -> Jsont_pointer.to_uri_fragment p
94
+
in
95
+
let result2 = Jsont_pointer.of_uri_fragment frag in
96
+
let s2 = to_string_of_result result2 in
80
97
if s2 = pointer_str then
81
98
Printf.printf "OK: %s -> %s\n" pointer_str frag
82
99
else
···
88
105
let test_add json_str pointer_str value_str =
89
106
try
90
107
let json = parse_json json_str in
91
-
let p = Jsont_pointer.of_string pointer_str in
92
108
let value = parse_json value_str in
93
-
let result = Jsont_pointer.add p json ~value in
109
+
let result = match Jsont_pointer.of_string pointer_str with
110
+
| `Nav p -> Jsont_pointer.add p json ~value
111
+
| `Append p -> Jsont_pointer.add p json ~value
112
+
in
94
113
Printf.printf "%s\n" (json_to_string result)
95
114
with Jsont.Error e ->
96
115
Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e)
···
99
118
let test_remove json_str pointer_str =
100
119
try
101
120
let json = parse_json json_str in
102
-
let p = Jsont_pointer.of_string pointer_str in
121
+
let p = Jsont_pointer.of_string_nav pointer_str in
103
122
let result = Jsont_pointer.remove p json in
104
123
Printf.printf "%s\n" (json_to_string result)
105
124
with Jsont.Error e ->
···
109
128
let test_replace json_str pointer_str value_str =
110
129
try
111
130
let json = parse_json json_str in
112
-
let p = Jsont_pointer.of_string pointer_str in
131
+
let p = Jsont_pointer.of_string_nav pointer_str in
113
132
let value = parse_json value_str in
114
133
let result = Jsont_pointer.replace p json ~value in
115
134
Printf.printf "%s\n" (json_to_string result)
···
120
139
let test_move json_str from_str path_str =
121
140
try
122
141
let json = parse_json json_str in
123
-
let from = Jsont_pointer.of_string from_str in
124
-
let path = Jsont_pointer.of_string path_str in
125
-
let result = Jsont_pointer.move ~from ~path json in
142
+
let from = Jsont_pointer.of_string_nav from_str in
143
+
let result = match Jsont_pointer.of_string path_str with
144
+
| `Nav path -> Jsont_pointer.move ~from ~path json
145
+
| `Append path -> Jsont_pointer.move ~from ~path json
146
+
in
126
147
Printf.printf "%s\n" (json_to_string result)
127
148
with Jsont.Error e ->
128
149
Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e)
···
131
152
let test_copy json_str from_str path_str =
132
153
try
133
154
let json = parse_json json_str in
134
-
let from = Jsont_pointer.of_string from_str in
135
-
let path = Jsont_pointer.of_string path_str in
136
-
let result = Jsont_pointer.copy ~from ~path json in
155
+
let from = Jsont_pointer.of_string_nav from_str in
156
+
let result = match Jsont_pointer.of_string path_str with
157
+
| `Nav path -> Jsont_pointer.copy ~from ~path json
158
+
| `Append path -> Jsont_pointer.copy ~from ~path json
159
+
in
137
160
Printf.printf "%s\n" (json_to_string result)
138
161
with Jsont.Error e ->
139
162
Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e)
···
142
165
let test_test json_str pointer_str expected_str =
143
166
try
144
167
let json = parse_json json_str in
145
-
let p = Jsont_pointer.of_string pointer_str in
168
+
let p = Jsont_pointer.of_string_nav pointer_str in
146
169
let expected = parse_json expected_str in
147
170
let result = Jsont_pointer.test p json ~expected in
148
171
Printf.printf "%b\n" result
···
153
176
let test_has json_str pointer_str =
154
177
try
155
178
let json = parse_json json_str in
156
-
let p = Jsont_pointer.of_string pointer_str in
179
+
let p = Jsont_pointer.of_string_nav pointer_str in
157
180
let result = Jsont_pointer.find p json in
158
181
Printf.printf "%b\n" (Option.is_some result)
159
182
with Jsont.Error e ->
+218
-107
doc/tutorial.md
+218
-107
doc/tutorial.md
···
4
4
[RFC 6901](https://www.rfc-editor.org/rfc/rfc6901), and demonstrates
5
5
the `jsont-pointer` OCaml library through interactive examples.
6
6
7
+
## JSON Pointer vs JSON Path
8
+
9
+
Before diving in, it's worth understanding the difference between JSON
10
+
Pointer and JSON Path, as they serve different purposes:
11
+
12
+
**JSON Pointer** (RFC 6901) is an *indicator syntax* that specifies a
13
+
*single location* within JSON data. It always identifies at most one
14
+
value.
15
+
16
+
**JSON Path** is a *query syntax* that can *search* JSON data and return
17
+
*multiple* values matching specified criteria.
18
+
19
+
Use JSON Pointer when you need to address a single, specific location
20
+
(like JSON Schema's `$ref`). Use JSON Path when you might need multiple
21
+
results (like Kubernetes queries).
22
+
23
+
The `jsont-pointer` library implements JSON Pointer and integrates with
24
+
the `Jsont.Path` type for representing navigation indices.
25
+
7
26
## Setup
8
27
9
28
First, let's set up our environment with helper functions:
10
29
11
30
```ocaml
12
31
# open Jsont_pointer;;
13
-
# #install_printer Jsont_pointer_top.printer;;
32
+
# #install_printer Jsont_pointer_top.nav_printer;;
33
+
# #install_printer Jsont_pointer_top.append_printer;;
14
34
# #install_printer Jsont_pointer_top.json_printer;;
15
35
# #install_printer Jsont_pointer_top.error_printer;;
16
36
# let parse_json s =
···
47
67
The JSON Pointer `/users/0/name` refers to the string `"Alice"`:
48
68
49
69
```ocaml
50
-
# let ptr = of_string "/users/0/name";;
51
-
val ptr : t = [`Mem "users"; `Nth 0; `Mem "name"]
70
+
# let ptr = of_string_nav "/users/0/name";;
71
+
val ptr : nav t = [Mem "users"; Nth 0; Mem "name"]
52
72
# get ptr users_json;;
53
73
- : Jsont.json = "Alice"
54
74
```
55
75
56
-
In OCaml, this is represented by the `Jsont_pointer.t` type - a sequence
57
-
of navigation steps from the document root to a target value.
76
+
In OCaml, this is represented by the `'a Jsont_pointer.t` type - a sequence
77
+
of navigation steps from the document root to a target value. The phantom
78
+
type parameter `'a` encodes whether this is a navigation pointer or an
79
+
append pointer (more on this later).
58
80
59
81
## Syntax: Reference Tokens
60
82
···
78
100
Let's see this in action:
79
101
80
102
```ocaml
81
-
# of_string "";;
82
-
- : t = []
103
+
# of_string_nav "";;
104
+
- : nav t = []
83
105
```
84
106
85
107
The empty pointer has no reference tokens - it points to the root.
86
108
87
109
```ocaml
88
-
# of_string "/foo";;
89
-
- : t = [`Mem "foo"]
110
+
# of_string_nav "/foo";;
111
+
- : nav t = [Mem "foo"]
90
112
```
91
113
92
114
The pointer `/foo` has one token: `foo`. Since it's not a number, it's
93
115
interpreted as an object member name (`Mem`).
94
116
95
117
```ocaml
96
-
# of_string "/foo/0";;
97
-
- : t = [`Mem "foo"; `Nth 0]
118
+
# of_string_nav "/foo/0";;
119
+
- : nav t = [Mem "foo"; Nth 0]
98
120
```
99
121
100
122
Here we have two tokens: `foo` (a member name) and `0` (interpreted as
101
123
an array index `Nth`).
102
124
103
125
```ocaml
104
-
# of_string "/foo/bar/baz";;
105
-
- : t = [`Mem "foo"; `Mem "bar"; `Mem "baz"]
126
+
# of_string_nav "/foo/bar/baz";;
127
+
- : nav t = [Mem "foo"; Mem "bar"; Mem "baz"]
106
128
```
107
129
108
130
Multiple tokens navigate deeper into nested structures.
109
131
110
132
### The Index Type
111
133
112
-
Each reference token becomes an `Index.t` value in the library:
134
+
Each reference token is represented using `Jsont.Path.index`:
113
135
114
136
<!-- $MDX skip -->
115
137
```ocaml
116
-
type t = [
117
-
| `Mem of string (* Object member access *)
118
-
| `Nth of int (* Array index access *)
119
-
| `End (* The special "-" marker for append operations *)
120
-
]
138
+
type index = Jsont.Path.index
139
+
(* = Jsont.Path.Mem of string * Jsont.Meta.t
140
+
| Jsont.Path.Nth of int * Jsont.Meta.t *)
121
141
```
122
142
123
-
The `Mem` variant holds the **unescaped** member name - you work with the
124
-
actual key string (like `"a/b"`) and the library handles any escaping needed
143
+
The `Mem` constructor is for object member access, and `Nth` is for array
144
+
index access. The member name is **unescaped** - you work with the actual
145
+
key string (like `"a/b"`) and the library handles any escaping needed
125
146
for the JSON Pointer string representation.
126
147
127
148
### Invalid Syntax
···
129
150
What happens if a pointer doesn't start with `/`?
130
151
131
152
```ocaml
132
-
# of_string "foo";;
153
+
# of_string_nav "foo";;
133
154
Exception:
134
155
Jsont.Error Invalid JSON Pointer: must be empty or start with '/': foo.
135
156
```
···
140
161
141
162
```ocaml
142
163
# of_string_result "foo";;
143
-
- : (t, string) result =
164
+
- : ([ `Append of append t | `Nav of nav t ], string) result =
144
165
Error "Invalid JSON Pointer: must be empty or start with '/': foo"
145
166
# of_string_result "/valid";;
146
-
- : (t, string) result = Ok [`Mem "valid"]
167
+
- : ([ `Append of append t | `Nav of nav t ], string) result =
168
+
Ok (`Nav [Mem "valid"])
147
169
```
148
170
149
171
## Evaluation: Navigating JSON
···
190
212
### Object Member Access
191
213
192
214
```ocaml
193
-
# get (of_string "/foo") rfc_example ;;
215
+
# get (of_string_nav "/foo") rfc_example ;;
194
216
- : Jsont.json = ["bar","baz"]
195
217
```
196
218
···
199
221
### Array Index Access
200
222
201
223
```ocaml
202
-
# get (of_string "/foo/0") rfc_example ;;
224
+
# get (of_string_nav "/foo/0") rfc_example ;;
203
225
- : Jsont.json = "bar"
204
-
# get (of_string "/foo/1") rfc_example ;;
226
+
# get (of_string_nav "/foo/1") rfc_example ;;
205
227
- : Jsont.json = "baz"
206
228
```
207
229
···
212
234
JSON allows empty strings as object keys:
213
235
214
236
```ocaml
215
-
# get (of_string "/") rfc_example ;;
237
+
# get (of_string_nav "/") rfc_example ;;
216
238
- : Jsont.json = 0
217
239
```
218
240
···
224
246
The RFC example includes keys with `/` and `~` characters:
225
247
226
248
```ocaml
227
-
# get (of_string "/a~1b") rfc_example ;;
249
+
# get (of_string_nav "/a~1b") rfc_example ;;
228
250
- : Jsont.json = 1
229
251
```
230
252
···
232
254
[below](#escaping-special-characters).
233
255
234
256
```ocaml
235
-
# get (of_string "/m~0n") rfc_example ;;
257
+
# get (of_string_nav "/m~0n") rfc_example ;;
236
258
- : Jsont.json = 8
237
259
```
238
260
···
242
264
to worry about escaping. The `Mem` variant holds the literal key name:
243
265
244
266
```ocaml
245
-
# let slash_ptr = make [`Mem "a/b"];;
246
-
val slash_ptr : t = [`Mem "a/b"]
267
+
# let slash_ptr = make [mem "a/b"];;
268
+
val slash_ptr : nav t = [Mem "a/b"]
247
269
# to_string slash_ptr;;
248
270
- : string = "/a~1b"
249
271
# get slash_ptr rfc_example ;;
···
257
279
Most characters don't need escaping in JSON Pointer strings:
258
280
259
281
```ocaml
260
-
# get (of_string "/c%d") rfc_example ;;
282
+
# get (of_string_nav "/c%d") rfc_example ;;
261
283
- : Jsont.json = 2
262
-
# get (of_string "/e^f") rfc_example ;;
284
+
# get (of_string_nav "/e^f") rfc_example ;;
263
285
- : Jsont.json = 3
264
-
# get (of_string "/g|h") rfc_example ;;
286
+
# get (of_string_nav "/g|h") rfc_example ;;
265
287
- : Jsont.json = 4
266
-
# get (of_string "/ ") rfc_example ;;
288
+
# get (of_string_nav "/ ") rfc_example ;;
267
289
- : Jsont.json = 7
268
290
```
269
291
···
274
296
What happens when we try to access something that doesn't exist?
275
297
276
298
```ocaml
277
-
# get_result (of_string "/nonexistent") rfc_example;;
299
+
# get_result (of_string_nav "/nonexistent") rfc_example;;
278
300
- : (Jsont.json, Jsont.Error.t) result =
279
301
Error JSON Pointer: member 'nonexistent' not found
280
302
File "-":
281
-
# find (of_string "/nonexistent") rfc_example;;
303
+
# find (of_string_nav "/nonexistent") rfc_example;;
282
304
- : Jsont.json option = None
283
305
```
284
306
285
307
Or an out-of-bounds array index:
286
308
287
309
```ocaml
288
-
# find (of_string "/foo/99") rfc_example;;
310
+
# find (of_string_nav "/foo/99") rfc_example;;
289
311
- : Jsont.json option = None
290
312
```
291
313
292
314
Or try to index into a non-container:
293
315
294
316
```ocaml
295
-
# find (of_string "/foo/0/invalid") rfc_example;;
317
+
# find (of_string_nav "/foo/0/invalid") rfc_example;;
296
318
- : Jsont.json option = None
297
319
```
298
320
···
300
322
301
323
<!-- $MDX skip -->
302
324
```ocaml
303
-
val get : t -> Jsont.json -> Jsont.json
304
-
val get_result : t -> Jsont.json -> (Jsont.json, Jsont.Error.t) result
305
-
val find : t -> Jsont.json -> Jsont.json option
325
+
val get : nav t -> Jsont.json -> Jsont.json
326
+
val get_result : nav t -> Jsont.json -> (Jsont.json, Jsont.Error.t) result
327
+
val find : nav t -> Jsont.json -> Jsont.json option
306
328
```
307
329
308
330
### Array Index Rules
···
318
340
> note that leading zeros are not allowed
319
341
320
342
```ocaml
321
-
# of_string "/foo/0";;
322
-
- : t = [`Mem "foo"; `Nth 0]
343
+
# of_string_nav "/foo/0";;
344
+
- : nav t = [Mem "foo"; Nth 0]
323
345
```
324
346
325
347
Zero itself is fine.
326
348
327
349
```ocaml
328
-
# of_string "/foo/01";;
329
-
- : t = [`Mem "foo"; `Mem "01"]
350
+
# of_string_nav "/foo/01";;
351
+
- : nav t = [Mem "foo"; Mem "01"]
330
352
```
331
353
332
354
But `01` has a leading zero, so it's NOT treated as an array index - it
333
355
becomes a member name instead. This protects against accidental octal
334
356
interpretation.
335
357
336
-
## The End-of-Array Marker: `-`
358
+
## The End-of-Array Marker: `-` and Type Safety
337
359
338
360
RFC 6901, Section 4 introduces a special token:
339
361
340
362
> exactly the single character "-", making the new referenced value the
341
363
> (nonexistent) member after the last array element.
342
364
343
-
This is primarily useful for JSON Patch operations (RFC 6902). Let's see
344
-
how it parses:
365
+
This `-` marker is unique to JSON Pointer (JSON Path has no equivalent).
366
+
It's primarily useful for JSON Patch operations (RFC 6902) to append
367
+
elements to arrays.
368
+
369
+
### Navigation vs Append Pointers
345
370
371
+
The `jsont-pointer` library uses **phantom types** to encode the difference
372
+
between pointers that can be used for navigation and pointers that target
373
+
the "append position":
374
+
375
+
<!-- $MDX skip -->
346
376
```ocaml
347
-
# of_string "/foo/-";;
348
-
- : t = [`Mem "foo"; `End]
377
+
type nav (* A pointer to an existing element *)
378
+
type append (* A pointer ending with "-" (append position) *)
379
+
type 'a t (* Pointer with phantom type parameter *)
349
380
```
350
381
351
-
The `-` is recognized as a special `End` index.
352
-
353
-
However, you cannot evaluate a pointer containing `-` because it refers
354
-
to a position that doesn't exist:
382
+
When you parse a pointer, you get either a `nav t` or an `append t`:
355
383
356
384
```ocaml
357
-
# find (of_string "/foo/-") rfc_example;;
358
-
- : Jsont.json option = None
385
+
# of_string "/foo/0";;
386
+
- : [ `Append of append t | `Nav of nav t ] = `Nav [Mem "foo"; Nth 0]
387
+
# of_string "/foo/-";;
388
+
- : [ `Append of append t | `Nav of nav t ] = `Append [Mem "foo"] /-
359
389
```
360
390
361
-
The RFC explains this:
391
+
The `-` creates an `append` pointer. Note that in the internal
392
+
representation, the append position is tracked separately (shown as `/-`).
393
+
394
+
### Why Phantom Types?
395
+
396
+
The RFC explains that `-` refers to a *nonexistent* position:
362
397
363
398
> Note that the use of the "-" character to index an array will always
364
399
> result in such an error condition because by definition it refers to
365
400
> a nonexistent array element.
366
401
367
-
But we'll see later that `-` is very useful for mutation operations!
402
+
So you **cannot use `get` or `find`** with an append pointer - it makes
403
+
no sense to retrieve a value from a position that doesn't exist! The
404
+
library enforces this at compile time:
405
+
406
+
```ocaml
407
+
# (* This won't compile: get requires nav t, not append t *)
408
+
(* get (match of_string "/foo/-" with `Append p -> p | _ -> assert false) rfc_example;; *)
409
+
```
410
+
411
+
However, append pointers **are** valid for mutation operations like `add`:
412
+
413
+
```ocaml
414
+
# let arr_obj = parse_json {|{"foo":["a","b"]}|};;
415
+
val arr_obj : Jsont.json = {"foo":["a","b"]}
416
+
# match of_string "/foo/-" with
417
+
| `Append p -> add p arr_obj ~value:(Jsont.Json.string "c")
418
+
| `Nav _ -> assert false ;;
419
+
- : Jsont.json = {"foo":["a","b","c"]}
420
+
```
421
+
422
+
For convenience, use `of_string_nav` when you know a pointer shouldn't
423
+
contain `-`:
424
+
425
+
```ocaml
426
+
# of_string_nav "/foo/0";;
427
+
- : nav t = [Mem "foo"; Nth 0]
428
+
# of_string_nav "/foo/-";;
429
+
Exception:
430
+
Jsont.Error Invalid JSON Pointer: '-' not allowed in navigation pointer.
431
+
```
432
+
433
+
### Creating Append Pointers Programmatically
434
+
435
+
You can convert a navigation pointer to an append pointer using `at_end`:
436
+
437
+
```ocaml
438
+
# let nav_ptr = of_string_nav "/foo";;
439
+
val nav_ptr : nav t = [Mem "foo"]
440
+
# let app_ptr = at_end nav_ptr;;
441
+
val app_ptr : append t = [Mem "foo"] /-
442
+
# to_string app_ptr;;
443
+
- : string = "/foo/-"
444
+
```
368
445
369
446
## Mutation Operations
370
447
···
372
449
(JSON Patch) uses JSON Pointer for modifications. The `jsont-pointer`
373
450
library provides these operations.
374
451
452
+
### Which Pointer Type for Which Operation?
453
+
454
+
The phantom type system enforces correct usage:
455
+
456
+
| Operation | Accepts | Because |
457
+
|-----------|---------|---------|
458
+
| `get`, `find` | `nav t` only | Can't retrieve from non-existent position |
459
+
| `remove` | `nav t` only | Can't remove what doesn't exist |
460
+
| `replace` | `nav t` only | Can't replace what doesn't exist |
461
+
| `test` | `nav t` only | Can't test non-existent position |
462
+
| `add` | `_ t` (both) | Can add at existing position OR append |
463
+
| `set` | `_ t` (both) | Can set existing position OR append |
464
+
| `move`, `copy` | `from:nav t`, `path:_ t` | Source must exist, dest can be append |
465
+
375
466
### Add
376
467
377
468
The `add` operation inserts a value at a location:
···
379
470
```ocaml
380
471
# let obj = parse_json {|{"foo":"bar"}|};;
381
472
val obj : Jsont.json = {"foo":"bar"}
382
-
# add (of_string "/baz") obj ~value:(Jsont.Json.string "qux")
473
+
# add (of_string_nav "/baz") obj ~value:(Jsont.Json.string "qux")
383
474
;;
384
475
- : Jsont.json = {"foo":"bar","baz":"qux"}
385
476
```
···
389
480
```ocaml
390
481
# let arr_obj = parse_json {|{"foo":["a","b"]}|};;
391
482
val arr_obj : Jsont.json = {"foo":["a","b"]}
392
-
# add (of_string "/foo/1") arr_obj ~value:(Jsont.Json.string "X")
483
+
# add (of_string_nav "/foo/1") arr_obj ~value:(Jsont.Json.string "X")
393
484
;;
394
485
- : Jsont.json = {"foo":["a","X","b"]}
395
486
```
396
487
397
-
This is where the `-` marker shines - it appends to the end:
488
+
This is where the `-` marker and append pointers shine - they append to the end:
398
489
399
490
```ocaml
400
-
# add (of_string "/foo/-") arr_obj ~value:(Jsont.Json.string "c")
491
+
# match of_string "/foo/-" with
492
+
| `Append p -> add p arr_obj ~value:(Jsont.Json.string "c")
493
+
| `Nav _ -> assert false ;;
494
+
- : Jsont.json = {"foo":["a","b","c"]}
495
+
```
496
+
497
+
Or more conveniently using `at_end`:
498
+
499
+
```ocaml
500
+
# add (at_end (of_string_nav "/foo")) arr_obj ~value:(Jsont.Json.string "c")
401
501
;;
402
502
- : Jsont.json = {"foo":["a","b","c"]}
403
503
```
404
504
405
505
### Remove
406
506
407
-
The `remove` operation deletes a value:
507
+
The `remove` operation deletes a value. It only accepts `nav t` because
508
+
you can only remove something that exists:
408
509
409
510
```ocaml
410
511
# let two_fields = parse_json {|{"foo":"bar","baz":"qux"}|};;
411
512
val two_fields : Jsont.json = {"foo":"bar","baz":"qux"}
412
-
# remove (of_string "/baz") two_fields ;;
513
+
# remove (of_string_nav "/baz") two_fields ;;
413
514
- : Jsont.json = {"foo":"bar"}
414
515
```
415
516
···
418
519
```ocaml
419
520
# let three_elem = parse_json {|{"foo":["a","b","c"]}|};;
420
521
val three_elem : Jsont.json = {"foo":["a","b","c"]}
421
-
# remove (of_string "/foo/1") three_elem ;;
522
+
# remove (of_string_nav "/foo/1") three_elem ;;
422
523
- : Jsont.json = {"foo":["a","c"]}
423
524
```
424
525
···
427
528
The `replace` operation updates an existing value:
428
529
429
530
```ocaml
430
-
# replace (of_string "/foo") obj ~value:(Jsont.Json.string "baz")
531
+
# replace (of_string_nav "/foo") obj ~value:(Jsont.Json.string "baz")
431
532
;;
432
533
- : Jsont.json = {"foo":"baz"}
433
534
```
434
535
435
-
Unlike `add`, `replace` requires the target to already exist.
536
+
Unlike `add`, `replace` requires the target to already exist (hence `nav t`).
436
537
Attempting to replace a nonexistent path raises an error.
437
538
438
539
### Move
439
540
440
-
The `move` operation relocates a value:
541
+
The `move` operation relocates a value. The source (`from`) must be a `nav t`
542
+
(you can only move something that exists), but the destination (`path`) can
543
+
be either:
441
544
442
545
```ocaml
443
546
# let nested = parse_json {|{"foo":{"bar":"baz"},"qux":{}}|};;
444
547
val nested : Jsont.json = {"foo":{"bar":"baz"},"qux":{}}
445
-
# move ~from:(of_string "/foo/bar") ~path:(of_string "/qux/thud") nested
548
+
# move ~from:(of_string_nav "/foo/bar") ~path:(of_string_nav "/qux/thud") nested
446
549
;;
447
550
- : Jsont.json = {"foo":{},"qux":{"thud":"baz"}}
448
551
```
449
552
450
553
### Copy
451
554
452
-
The `copy` operation duplicates a value:
555
+
The `copy` operation duplicates a value (same typing as `move`):
453
556
454
557
```ocaml
455
558
# let to_copy = parse_json {|{"foo":{"bar":"baz"}}|};;
456
559
val to_copy : Jsont.json = {"foo":{"bar":"baz"}}
457
-
# copy ~from:(of_string "/foo/bar") ~path:(of_string "/foo/qux") to_copy
560
+
# copy ~from:(of_string_nav "/foo/bar") ~path:(of_string_nav "/foo/qux") to_copy
458
561
;;
459
562
- : Jsont.json = {"foo":{"bar":"baz","qux":"baz"}}
460
563
```
···
464
567
The `test` operation verifies a value (useful in JSON Patch):
465
568
466
569
```ocaml
467
-
# test (of_string "/foo") obj ~expected:(Jsont.Json.string "bar");;
570
+
# test (of_string_nav "/foo") obj ~expected:(Jsont.Json.string "bar");;
468
571
- : bool = true
469
-
# test (of_string "/foo") obj ~expected:(Jsont.Json.string "wrong");;
572
+
# test (of_string_nav "/foo") obj ~expected:(Jsont.Json.string "wrong");;
470
573
- : bool = false
471
574
```
472
575
···
493
596
and escaping happens automatically during serialization:
494
597
495
598
```ocaml
496
-
# let p = make [`Mem "a/b"];;
497
-
val p : t = [`Mem "a/b"]
599
+
# let p = make [mem "a/b"];;
600
+
val p : nav t = [Mem "a/b"]
498
601
# to_string p;;
499
602
- : string = "/a~1b"
500
-
# of_string "/a~1b";;
501
-
- : t = [`Mem "a/b"]
603
+
# of_string_nav "/a~1b";;
604
+
- : nav t = [Mem "a/b"]
502
605
```
503
606
504
607
### Escaping in Action
···
561
664
This adds percent-encoding on top of the `~0`/`~1` escaping:
562
665
563
666
```ocaml
564
-
# to_uri_fragment (of_string "/foo");;
667
+
# to_uri_fragment (of_string_nav "/foo");;
565
668
- : string = "/foo"
566
-
# to_uri_fragment (of_string "/a~1b");;
669
+
# to_uri_fragment (of_string_nav "/a~1b");;
567
670
- : string = "/a~1b"
568
-
# to_uri_fragment (of_string "/c%d");;
671
+
# to_uri_fragment (of_string_nav "/c%d");;
569
672
- : string = "/c%25d"
570
-
# to_uri_fragment (of_string "/ ");;
673
+
# to_uri_fragment (of_string_nav "/ ");;
571
674
- : string = "/%20"
572
675
```
573
676
···
592
695
Instead of parsing strings, you can build pointers from indices:
593
696
594
697
```ocaml
595
-
# let port_ptr = make [`Mem "database"; `Mem "port"];;
596
-
val port_ptr : t = [`Mem "database"; `Mem "port"]
698
+
# let port_ptr = make [mem "database"; mem "port"];;
699
+
val port_ptr : nav t = [Mem "database"; Mem "port"]
597
700
# to_string port_ptr;;
598
701
- : string = "/database/port"
599
702
```
600
703
601
-
For array access, use `Nth`:
704
+
For array access, use the `nth` helper:
602
705
603
706
```ocaml
604
-
# let first_feature_ptr = make [`Mem "features"; `Nth 0];;
605
-
val first_feature_ptr : t = [`Mem "features"; `Nth 0]
707
+
# let first_feature_ptr = make [mem "features"; nth 0];;
708
+
val first_feature_ptr : nav t = [Mem "features"; Nth 0]
606
709
# to_string first_feature_ptr;;
607
710
- : string = "/features/0"
608
711
```
609
712
610
713
### Pointer Navigation
611
714
612
-
You can build pointers incrementally using `append`:
715
+
You can build pointers incrementally using the `/` operator (or `append_index`):
613
716
614
717
```ocaml
615
-
# let db_ptr = of_string "/database";;
616
-
val db_ptr : t = [`Mem "database"]
617
-
# let creds_ptr = append db_ptr (`Mem "credentials");;
618
-
val creds_ptr : t = [`Mem "database"; `Mem "credentials"]
619
-
# let user_ptr = append creds_ptr (`Mem "username");;
620
-
val user_ptr : t = [`Mem "database"; `Mem "credentials"; `Mem "username"]
718
+
# let db_ptr = of_string_nav "/database";;
719
+
val db_ptr : nav t = [Mem "database"]
720
+
# let creds_ptr = db_ptr / mem "credentials";;
721
+
val creds_ptr : nav t = [Mem "database"; Mem "credentials"]
722
+
# let user_ptr = creds_ptr / mem "username";;
723
+
val user_ptr : nav t = [Mem "database"; Mem "credentials"; Mem "username"]
621
724
# to_string user_ptr;;
622
725
- : string = "/database/credentials/username"
623
726
```
···
625
728
Or concatenate two pointers:
626
729
627
730
```ocaml
628
-
# let base = of_string "/api/v1";;
629
-
val base : t = [`Mem "api"; `Mem "v1"]
630
-
# let endpoint = of_string "/users/0";;
631
-
val endpoint : t = [`Mem "users"; `Nth 0]
731
+
# let base = of_string_nav "/api/v1";;
732
+
val base : nav t = [Mem "api"; Mem "v1"]
733
+
# let endpoint = of_string_nav "/users/0";;
734
+
val endpoint : nav t = [Mem "users"; Nth 0]
632
735
# to_string (concat base endpoint);;
633
736
- : string = "/api/v1/users/0"
634
737
```
···
660
763
```ocaml
661
764
# let db_host =
662
765
Jsont.Json.decode
663
-
(path (of_string "/database/host") Jsont.string)
766
+
(path (of_string_nav "/database/host") Jsont.string)
664
767
config_json
665
768
|> Result.get_ok;;
666
769
val db_host : string = "localhost"
667
770
# let db_port =
668
771
Jsont.Json.decode
669
-
(path (of_string "/database/port") Jsont.int)
772
+
(path (of_string_nav "/database/port") Jsont.int)
670
773
config_json
671
774
|> Result.get_ok;;
672
775
val db_port : int = 5432
···
677
780
```ocaml
678
781
# let features =
679
782
Jsont.Json.decode
680
-
(path (of_string "/features") Jsont.(list string))
783
+
(path (of_string_nav "/features") Jsont.(list string))
681
784
config_json
682
785
|> Result.get_ok;;
683
786
val features : string list = ["auth"; "logging"; "metrics"]
···
690
793
```ocaml
691
794
# let timeout =
692
795
Jsont.Json.decode
693
-
(path ~absent:30 (of_string "/database/timeout") Jsont.int)
796
+
(path ~absent:30 (of_string_nav "/database/timeout") Jsont.int)
694
797
config_json
695
798
|> Result.get_ok;;
696
799
val timeout : int = 30
···
710
813
val org_json : Jsont.json =
711
814
{"organization":{"owner":{"name":"Alice","email":"alice@example.com","age":35},"members":[{"name":"Bob","email":"bob@example.com","age":28}]}}
712
815
# Jsont.Json.decode
713
-
(path (of_string "/organization/owner/name") Jsont.string)
816
+
(path (of_string_nav "/organization/owner/name") Jsont.string)
714
817
org_json
715
818
|> Result.get_ok;;
716
819
- : string = "Alice"
717
820
# Jsont.Json.decode
718
-
(path (of_string "/organization/members/0/age") Jsont.int)
821
+
(path (of_string_nav "/organization/members/0/age") Jsont.int)
719
822
org_json
720
823
|> Result.get_ok;;
721
824
- : int = 28
···
727
830
728
831
```ocaml
729
832
# let raw_port =
730
-
match get (of_string "/database/port") config_json with
833
+
match get (of_string_nav "/database/port") config_json with
731
834
| Jsont.Number (f, _) -> int_of_float f
732
835
| _ -> failwith "expected number";;
733
836
val raw_port : int = 5432
···
738
841
```ocaml
739
842
# let typed_port =
740
843
Jsont.Json.decode
741
-
(path (of_string "/database/port") Jsont.int)
844
+
(path (of_string_nav "/database/port") Jsont.int)
742
845
config_json
743
846
|> Result.get_ok;;
744
847
val typed_port : int = 5432
···
756
859
3. **Evaluation**: Tokens navigate through objects (by key) and arrays (by index)
757
860
4. **URI Encoding**: Pointers can be percent-encoded for use in URIs
758
861
5. **Mutations**: Combined with JSON Patch (RFC 6902), pointers enable structured updates
862
+
6. **Type Safety**: Phantom types (`nav t` vs `append t`) prevent misuse of append pointers with retrieval operations
759
863
760
864
The `jsont-pointer` library implements all of this with type-safe OCaml
761
865
interfaces, integration with the `jsont` codec system, and proper error
762
866
handling for malformed pointers and missing values.
867
+
868
+
### Key Points on JSON Pointer vs JSON Path
869
+
870
+
- **JSON Pointer** addresses a *single* location (like a file path)
871
+
- **JSON Path** queries for *multiple* values (like a search)
872
+
- The `-` token is unique to JSON Pointer - it means "append position" for arrays
873
+
- The library uses phantom types to enforce that `-` (append) pointers cannot be used with `get`/`find`
+231
-216
src/jsont_pointer.ml
+231
-216
src/jsont_pointer.ml
···
54
54
else None
55
55
end
56
56
57
-
(* Index type - represents how a token is interpreted in context *)
58
-
module Index = struct
59
-
type t = [ `Mem of string | `Nth of int | `End ]
60
-
61
-
let pp ppf = function
62
-
| `Mem s -> Format.fprintf ppf "/%s" (Token.escape s)
63
-
| `Nth n -> Format.fprintf ppf "/%d" n
64
-
| `End -> Format.fprintf ppf "/-"
57
+
(* Index type - directly reuses Jsont.Path.index *)
58
+
type index = Jsont.Path.index
65
59
66
-
let equal i1 i2 = match i1, i2 with
67
-
| `Mem s1, `Mem s2 -> String.equal s1 s2
68
-
| `Nth n1, `Nth n2 -> Int.equal n1 n2
69
-
| `End, `End -> true
70
-
| _ -> false
60
+
(* Convenience constructors *)
61
+
let mem ?(meta = Jsont.Meta.none) s : index = Jsont.Path.Mem (s, meta)
62
+
let nth ?(meta = Jsont.Meta.none) n : index = Jsont.Path.Nth (n, meta)
71
63
72
-
let compare i1 i2 = match i1, i2 with
73
-
| `Mem s1, `Mem s2 -> String.compare s1 s2
74
-
| `Mem _, _ -> -1
75
-
| _, `Mem _ -> 1
76
-
| `Nth n1, `Nth n2 -> Int.compare n1 n2
77
-
| `Nth _, `End -> -1
78
-
| `End, `Nth _ -> 1
79
-
| `End, `End -> 0
64
+
let pp_index ppf = function
65
+
| Jsont.Path.Mem (s, _) -> Format.fprintf ppf "/%s" (Token.escape s)
66
+
| Jsont.Path.Nth (n, _) -> Format.fprintf ppf "/%d" n
80
67
81
-
let of_path_index (idx : Jsont.Path.index) : t =
82
-
match idx with
83
-
| Jsont.Path.Mem (s, _meta) -> `Mem s
84
-
| Jsont.Path.Nth (n, _meta) -> `Nth n
68
+
let equal_index i1 i2 = match i1, i2 with
69
+
| Jsont.Path.Mem (s1, _), Jsont.Path.Mem (s2, _) -> String.equal s1 s2
70
+
| Jsont.Path.Nth (n1, _), Jsont.Path.Nth (n2, _) -> Int.equal n1 n2
71
+
| _ -> false
85
72
86
-
let to_path_index (idx : t) : Jsont.Path.index option =
87
-
match idx with
88
-
| `Mem s -> Some (Jsont.Path.Mem (s, Jsont.Meta.none))
89
-
| `Nth n -> Some (Jsont.Path.Nth (n, Jsont.Meta.none))
90
-
| `End -> None
91
-
end
73
+
let compare_index i1 i2 = match i1, i2 with
74
+
| Jsont.Path.Mem (s1, _), Jsont.Path.Mem (s2, _) -> String.compare s1 s2
75
+
| Jsont.Path.Mem _, Jsont.Path.Nth _ -> -1
76
+
| Jsont.Path.Nth _, Jsont.Path.Mem _ -> 1
77
+
| Jsont.Path.Nth (n1, _), Jsont.Path.Nth (n2, _) -> Int.compare n1 n2
92
78
93
-
(* Internal representation: raw unescaped tokens.
94
-
Per RFC 6901, interpretation as member name vs array index
95
-
depends on the JSON value type at evaluation time. *)
79
+
(* Internal representation: raw unescaped tokens *)
96
80
module Segment = struct
97
-
type t =
98
-
| Token of string (* Unescaped reference token *)
99
-
| End (* The "-" token for end-of-array *)
100
-
101
-
let of_escaped_string s =
102
-
if s = "-" then End
103
-
else Token (Token.unescape s)
81
+
type t = string (* Unescaped reference token *)
104
82
105
-
let to_escaped_string = function
106
-
| Token s -> Token.escape s
107
-
| End -> "-"
83
+
let of_escaped_string s = Token.unescape s
108
84
109
-
(* Convert to Index for a given JSON value type *)
110
-
let to_index seg ~for_array =
111
-
match seg with
112
-
| End -> `End
113
-
| Token s ->
114
-
if for_array then
115
-
match Token.is_valid_array_index s with
116
-
| Some n -> `Nth n
117
-
| None -> `Mem s (* Invalid index becomes member for error msg *)
118
-
else
119
-
`Mem s
85
+
let to_escaped_string s = Token.escape s
120
86
121
-
(* Convert from Index *)
122
87
let of_index = function
123
-
| `End -> End
124
-
| `Mem s -> Token s
125
-
| `Nth n -> Token (string_of_int n)
88
+
| Jsont.Path.Mem (s, _) -> s
89
+
| Jsont.Path.Nth (n, _) -> string_of_int n
90
+
91
+
let to_index s : index =
92
+
match Token.is_valid_array_index s with
93
+
| Some n -> nth n
94
+
| None -> mem s
126
95
end
127
96
128
-
(* Pointer type - list of segments *)
129
-
type t = Segment.t list
97
+
(* Phantom types *)
98
+
type nav
99
+
type append
130
100
131
-
let root = []
101
+
(* Pointer type with phantom type parameter *)
102
+
type _ t = {
103
+
segments : Segment.t list;
104
+
is_append : bool; (* true if ends with "-" *)
105
+
}
132
106
133
-
let is_root p = p = []
107
+
let root = { segments = []; is_append = false }
134
108
135
-
(* Convert indices to segments *)
136
-
let make indices = List.map Segment.of_index indices
109
+
let is_root p = p.segments = [] && not p.is_append
137
110
138
-
(* Convert segments to indices, assuming array context for numeric tokens *)
139
-
let indices p = List.map (fun seg -> Segment.to_index seg ~for_array:true) p
111
+
let make indices =
112
+
{ segments = List.map Segment.of_index indices; is_append = false }
140
113
141
-
let append p idx = p @ [Segment.of_index idx]
114
+
let ( / ) p idx =
115
+
{ segments = p.segments @ [Segment.of_index idx]; is_append = false }
142
116
143
-
let concat p1 p2 = p1 @ p2
117
+
let append_index = ( / )
144
118
145
-
let parent p = match List.rev p with
119
+
let at_end p =
120
+
{ segments = p.segments; is_append = true }
121
+
122
+
let concat p1 p2 =
123
+
{ segments = p1.segments @ p2.segments; is_append = false }
124
+
125
+
let parent p =
126
+
match List.rev p.segments with
146
127
| [] -> None
147
-
| _ :: rest -> Some (List.rev rest)
128
+
| _ :: rest -> Some { segments = List.rev rest; is_append = false }
148
129
149
-
let last p = match List.rev p with
130
+
let last p =
131
+
match List.rev p.segments with
150
132
| [] -> None
151
-
| seg :: _ -> Some (Segment.to_index seg ~for_array:true)
133
+
| seg :: _ -> Some (Segment.to_index seg)
134
+
135
+
let indices (type a) (p : a t) = List.map Segment.to_index p.segments
152
136
153
137
(* Parsing *)
154
138
155
-
let of_string s =
156
-
if s = "" then root
139
+
let parse_segments s =
140
+
if s = "" then []
157
141
else if s.[0] <> '/' then
158
142
Jsont.Error.msgf Jsont.Meta.none
159
143
"Invalid JSON Pointer: must be empty or start with '/': %s" s
···
162
146
let tokens = String.split_on_char '/' rest in
163
147
List.map Segment.of_escaped_string tokens
164
148
149
+
let of_string s : [ `Nav of nav t | `Append of append t ] =
150
+
let segments = parse_segments s in
151
+
(* Check if ends with "-" *)
152
+
match List.rev segments with
153
+
| "-" :: rest ->
154
+
(* Validate that "-" only appears at the end *)
155
+
if List.exists (( = ) "-") rest then
156
+
Jsont.Error.msgf Jsont.Meta.none
157
+
"Invalid JSON Pointer: '-' can only appear at the end";
158
+
`Append { segments = List.rev rest; is_append = true }
159
+
| _ ->
160
+
(* Validate no "-" anywhere *)
161
+
if List.exists (( = ) "-") segments then
162
+
Jsont.Error.msgf Jsont.Meta.none
163
+
"Invalid JSON Pointer: '-' can only appear at the end";
164
+
`Nav { segments; is_append = false }
165
+
166
+
let of_string_nav s : nav t =
167
+
match of_string s with
168
+
| `Nav p -> p
169
+
| `Append _ ->
170
+
Jsont.Error.msgf Jsont.Meta.none
171
+
"Invalid JSON Pointer: '-' not allowed in navigation pointer"
172
+
165
173
let of_string_result s =
166
174
try Ok (of_string s)
167
175
with Jsont.Error e -> Error (Jsont.Error.to_string e)
···
195
203
in
196
204
loop 0
197
205
198
-
let of_uri_fragment s =
199
-
of_string (percent_decode s)
206
+
let of_uri_fragment s = of_string (percent_decode s)
207
+
208
+
let of_uri_fragment_nav s = of_string_nav (percent_decode s)
200
209
201
210
let of_uri_fragment_result s =
202
211
try Ok (of_uri_fragment s)
···
204
213
205
214
(* Serialization *)
206
215
207
-
let to_string p =
208
-
if p = [] then ""
209
-
else
210
-
let b = Buffer.create 64 in
211
-
List.iter (fun seg ->
212
-
Buffer.add_char b '/';
213
-
Buffer.add_string b (Segment.to_escaped_string seg)
214
-
) p;
215
-
Buffer.contents b
216
+
let to_string (type a) (p : a t) =
217
+
let base =
218
+
if p.segments = [] then ""
219
+
else
220
+
let b = Buffer.create 64 in
221
+
List.iter (fun seg ->
222
+
Buffer.add_char b '/';
223
+
Buffer.add_string b (Segment.to_escaped_string seg)
224
+
) p.segments;
225
+
Buffer.contents b
226
+
in
227
+
if p.is_append then base ^ "/-" else base
216
228
217
229
(* URI fragment percent-encoding *)
218
230
let needs_percent_encoding c =
219
-
(* RFC 3986 fragment: unreserved / pct-encoded / sub-delims / ":" / "@" / "/" / "?" *)
220
-
(* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" *)
221
-
(* sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" *)
222
231
not (
223
232
(c >= 'A' && c <= 'Z') ||
224
233
(c >= 'a' && c <= 'z') ||
···
247
256
) s;
248
257
Buffer.contents b
249
258
250
-
let to_uri_fragment p =
251
-
percent_encode (to_string p)
259
+
let to_uri_fragment p = percent_encode (to_string p)
252
260
253
-
let pp ppf p =
254
-
Format.pp_print_string ppf (to_string p)
261
+
let pp ppf p = Format.pp_print_string ppf (to_string p)
255
262
256
-
let pp_verbose ppf p =
257
-
let pp_index ppf = function
258
-
| `Mem s -> Format.fprintf ppf {|`Mem "%s"|} s
259
-
| `Nth n -> Format.fprintf ppf "`Nth %d" n
260
-
| `End -> Format.fprintf ppf "`End"
263
+
let pp_verbose (type a) ppf (p : a t) =
264
+
let pp_idx ppf seg =
265
+
match Token.is_valid_array_index seg with
266
+
| Some n -> Format.fprintf ppf "Nth %d" n
267
+
| None -> Format.fprintf ppf {|Mem "%s"|} seg
261
268
in
262
-
Format.fprintf ppf "[%a]"
263
-
(Format.pp_print_list ~pp_sep:(fun ppf () -> Format.fprintf ppf "; ") pp_index)
264
-
(indices p)
269
+
Format.fprintf ppf "[%a]%s"
270
+
(Format.pp_print_list ~pp_sep:(fun ppf () -> Format.fprintf ppf "; ") pp_idx)
271
+
p.segments
272
+
(if p.is_append then " /-" else "")
265
273
266
274
(* Comparison *)
267
275
268
-
let segment_equal s1 s2 = match s1, s2 with
269
-
| Segment.Token t1, Segment.Token t2 -> String.equal t1 t2
270
-
| Segment.End, Segment.End -> true
271
-
| _ -> false
276
+
let equal (type a b) (p1 : a t) (p2 : b t) =
277
+
List.equal String.equal p1.segments p2.segments &&
278
+
p1.is_append = p2.is_append
272
279
273
-
let segment_compare s1 s2 = match s1, s2 with
274
-
| Segment.Token t1, Segment.Token t2 -> String.compare t1 t2
275
-
| Segment.Token _, Segment.End -> -1
276
-
| Segment.End, Segment.Token _ -> 1
277
-
| Segment.End, Segment.End -> 0
278
-
279
-
let equal p1 p2 =
280
-
List.equal segment_equal p1 p2
281
-
282
-
let compare p1 p2 =
283
-
List.compare segment_compare p1 p2
280
+
let compare (type a b) (p1 : a t) (p2 : b t) =
281
+
match List.compare String.compare p1.segments p2.segments with
282
+
| 0 -> Bool.compare p1.is_append p2.is_append
283
+
| n -> n
284
284
285
285
(* Path conversion *)
286
286
287
-
let segment_of_path_index (idx : Jsont.Path.index) : Segment.t =
288
-
match idx with
289
-
| Jsont.Path.Mem (s, _meta) -> Segment.Token s
290
-
| Jsont.Path.Nth (n, _meta) -> Segment.Token (string_of_int n)
291
-
292
-
let of_path (p : Jsont.Path.t) : t =
293
-
List.rev_map segment_of_path_index (Jsont.Path.rev_indices p)
294
-
295
-
let to_path p =
296
-
let rec convert acc = function
297
-
| [] -> Some acc
298
-
| Segment.End :: _ -> None
299
-
| Segment.Token s :: rest ->
300
-
(* For path conversion, we need to decide if it's a member or index.
301
-
We use array context for numeric tokens since Jsont.Path distinguishes. *)
302
-
let acc' = match Token.is_valid_array_index s with
303
-
| Some n -> Jsont.Path.nth ~meta:Jsont.Meta.none n acc
304
-
| None -> Jsont.Path.mem ~meta:Jsont.Meta.none s acc
305
-
in
306
-
convert acc' rest
307
-
in
308
-
convert Jsont.Path.root p
287
+
let of_path (p : Jsont.Path.t) : nav t =
288
+
let segments = List.rev_map Segment.of_index (Jsont.Path.rev_indices p) in
289
+
{ segments; is_append = false }
309
290
310
-
let to_path_exn p =
311
-
match to_path p with
312
-
| Some path -> path
313
-
| None ->
314
-
Jsont.Error.msgf Jsont.Meta.none
315
-
"Cannot convert JSON Pointer with '-' index to Jsont.Path"
291
+
let to_path (p : nav t) : Jsont.Path.t =
292
+
List.fold_left (fun acc seg ->
293
+
match Token.is_valid_array_index seg with
294
+
| Some n -> Jsont.Path.nth n acc
295
+
| None -> Jsont.Path.mem seg acc
296
+
) Jsont.Path.root p.segments
316
297
317
298
(* Evaluation helpers *)
318
299
···
332
313
if n < 0 || n >= List.length arr then None
333
314
else Some (List.nth arr n)
334
315
335
-
(* Evaluation *)
316
+
(* Evaluation - only for nav pointers *)
336
317
337
-
let rec eval_get p json =
338
-
match p with
318
+
let rec eval_get segments json =
319
+
match segments with
339
320
| [] -> json
340
-
| Segment.End :: _ ->
341
-
Jsont.Error.msgf (Jsont.Json.meta json)
342
-
"JSON Pointer: '-' (end marker) refers to nonexistent array element"
343
-
| Segment.Token token :: rest ->
321
+
| token :: rest ->
344
322
(match json with
345
323
| Jsont.Object (members, _) ->
346
-
(* For objects, token is always a member name *)
347
324
(match get_member token members with
348
325
| Some (_, value) -> eval_get rest value
349
326
| None ->
350
327
Jsont.Error.msgf (Jsont.Json.meta json)
351
328
"JSON Pointer: member '%s' not found" token)
352
329
| Jsont.Array (elements, _) ->
353
-
(* For arrays, token must be a valid array index *)
354
330
(match Token.is_valid_array_index token with
355
331
| Some n ->
356
332
(match get_nth n elements with
···
367
343
"JSON Pointer: cannot index into %s with '%s'"
368
344
(json_sort_string json) token)
369
345
370
-
let get p json = eval_get p json
346
+
let get (p : nav t) json = eval_get p.segments json
371
347
372
348
let get_result p json =
373
349
try Ok (get p json)
···
431
407
Jsont.Error.msgf (Jsont.Json.meta json)
432
408
"JSON Pointer: cannot navigate through %s" (json_sort_string json)
433
409
434
-
(* Mutation: set *)
410
+
(* Mutation: set - works with any pointer type *)
435
411
436
-
let rec eval_set p value json =
437
-
match p with
438
-
| [] -> value
439
-
| [Segment.End] ->
412
+
let rec eval_set_segments segments is_append value json =
413
+
match segments, is_append with
414
+
| [], false -> value
415
+
| [], true ->
416
+
(* Append to array *)
440
417
(match json with
441
418
| Jsont.Array (elements, meta) -> Jsont.Array (elements @ [value], meta)
442
419
| _ ->
443
420
Jsont.Error.msgf (Jsont.Json.meta json)
444
421
"JSON Pointer: '-' can only be used on arrays, got %s"
445
422
(json_sort_string json))
446
-
| Segment.End :: _ ->
447
-
Jsont.Error.msgf (Jsont.Json.meta json)
448
-
"JSON Pointer: '-' (end marker) refers to nonexistent array element"
449
-
| [Segment.Token token] ->
423
+
| [token], false ->
450
424
navigate_to_child token json
451
425
~on_object:(fun members meta ->
452
426
if Option.is_some (get_member token members) then
···
463
437
~on_other:(fun () ->
464
438
Jsont.Error.msgf (Jsont.Json.meta json)
465
439
"JSON Pointer: cannot set in %s" (json_sort_string json))
466
-
| Segment.Token token :: rest ->
440
+
| [token], true ->
441
+
(* Navigate to token, then append *)
467
442
navigate_to_child token json
468
443
~on_object:(fun members meta ->
469
444
match get_member token members with
470
445
| Some (_, child) ->
471
-
Jsont.Object (set_member token (eval_set rest value child) members, meta)
446
+
let child' = eval_set_segments [] true value child in
447
+
Jsont.Object (set_member token child' members, meta)
472
448
| None -> error_member_not_found json token)
473
449
~on_array:(fun elements meta n ->
474
450
match get_nth n elements with
475
451
| Some child ->
476
-
Jsont.Array (replace_at n (eval_set rest value child) elements, meta)
452
+
let child' = eval_set_segments [] true value child in
453
+
Jsont.Array (replace_at n child' elements, meta)
454
+
| None -> error_index_out_of_bounds json n)
455
+
~on_other:(fun () -> error_cannot_navigate json)
456
+
| token :: rest, _ ->
457
+
navigate_to_child token json
458
+
~on_object:(fun members meta ->
459
+
match get_member token members with
460
+
| Some (_, child) ->
461
+
Jsont.Object (set_member token (eval_set_segments rest is_append value child) members, meta)
462
+
| None -> error_member_not_found json token)
463
+
~on_array:(fun elements meta n ->
464
+
match get_nth n elements with
465
+
| Some child ->
466
+
Jsont.Array (replace_at n (eval_set_segments rest is_append value child) elements, meta)
477
467
| None -> error_index_out_of_bounds json n)
478
468
~on_other:(fun () -> error_cannot_navigate json)
479
469
480
-
let set p json ~value = eval_set p value json
470
+
let set (type a) (p : a t) json ~value =
471
+
eval_set_segments p.segments p.is_append value json
481
472
482
-
(* Mutation: add (RFC 6902 semantics) *)
473
+
(* Mutation: add (RFC 6902 semantics) - works with any pointer type *)
483
474
484
-
let rec eval_add p value json =
485
-
match p with
486
-
| [] -> value
487
-
| [Segment.End] ->
475
+
let rec eval_add_segments segments is_append value json =
476
+
match segments, is_append with
477
+
| [], false -> value
478
+
| [], true ->
479
+
(* Append to array *)
488
480
(match json with
489
481
| Jsont.Array (elements, meta) -> Jsont.Array (elements @ [value], meta)
490
482
| _ ->
491
483
Jsont.Error.msgf (Jsont.Json.meta json)
492
484
"JSON Pointer: '-' can only be used on arrays, got %s"
493
485
(json_sort_string json))
494
-
| Segment.End :: _ ->
495
-
Jsont.Error.msgf (Jsont.Json.meta json)
496
-
"JSON Pointer: '-' in non-final position"
497
-
| [Segment.Token token] ->
486
+
| [token], false ->
498
487
navigate_to_child token json
499
488
~on_object:(fun members meta ->
500
489
Jsont.Object (set_member token value members, meta))
···
509
498
~on_other:(fun () ->
510
499
Jsont.Error.msgf (Jsont.Json.meta json)
511
500
"JSON Pointer: cannot add to %s" (json_sort_string json))
512
-
| Segment.Token token :: rest ->
501
+
| [token], true ->
502
+
(* Navigate to token, then append *)
513
503
navigate_to_child token json
514
504
~on_object:(fun members meta ->
515
505
match get_member token members with
516
506
| Some (_, child) ->
517
-
Jsont.Object (set_member token (eval_add rest value child) members, meta)
507
+
let child' = eval_add_segments [] true value child in
508
+
Jsont.Object (set_member token child' members, meta)
518
509
| None -> error_member_not_found json token)
519
510
~on_array:(fun elements meta n ->
520
511
match get_nth n elements with
521
512
| Some child ->
522
-
Jsont.Array (replace_at n (eval_add rest value child) elements, meta)
513
+
let child' = eval_add_segments [] true value child in
514
+
Jsont.Array (replace_at n child' elements, meta)
515
+
| None -> error_index_out_of_bounds json n)
516
+
~on_other:(fun () -> error_cannot_navigate json)
517
+
| token :: rest, _ ->
518
+
navigate_to_child token json
519
+
~on_object:(fun members meta ->
520
+
match get_member token members with
521
+
| Some (_, child) ->
522
+
Jsont.Object (set_member token (eval_add_segments rest is_append value child) members, meta)
523
+
| None -> error_member_not_found json token)
524
+
~on_array:(fun elements meta n ->
525
+
match get_nth n elements with
526
+
| Some child ->
527
+
Jsont.Array (replace_at n (eval_add_segments rest is_append value child) elements, meta)
523
528
| None -> error_index_out_of_bounds json n)
524
529
~on_other:(fun () -> error_cannot_navigate json)
525
530
526
-
let add p json ~value = eval_add p value json
531
+
let add (type a) (p : a t) json ~value =
532
+
eval_add_segments p.segments p.is_append value json
527
533
528
-
(* Mutation: remove *)
534
+
(* Mutation: remove - only for nav pointers *)
529
535
530
-
let rec eval_remove p json =
531
-
match p with
536
+
let rec eval_remove_segments segments json =
537
+
match segments with
532
538
| [] ->
533
539
Jsont.Error.msgf Jsont.Meta.none "JSON Pointer: cannot remove root document"
534
-
| [Segment.End] ->
535
-
Jsont.Error.msgf (Jsont.Json.meta json)
536
-
"JSON Pointer: '-' refers to nonexistent element"
537
-
| Segment.End :: _ ->
538
-
Jsont.Error.msgf (Jsont.Json.meta json)
539
-
"JSON Pointer: '-' in non-final position"
540
-
| [Segment.Token token] ->
540
+
| [token] ->
541
541
navigate_to_child token json
542
542
~on_object:(fun members meta ->
543
543
if Option.is_some (get_member token members) then
···
554
554
~on_other:(fun () ->
555
555
Jsont.Error.msgf (Jsont.Json.meta json)
556
556
"JSON Pointer: cannot remove from %s" (json_sort_string json))
557
-
| Segment.Token token :: rest ->
557
+
| token :: rest ->
558
558
navigate_to_child token json
559
559
~on_object:(fun members meta ->
560
560
match get_member token members with
561
561
| Some (_, child) ->
562
-
Jsont.Object (set_member token (eval_remove rest child) members, meta)
562
+
Jsont.Object (set_member token (eval_remove_segments rest child) members, meta)
563
563
| None -> error_member_not_found json token)
564
564
~on_array:(fun elements meta n ->
565
565
match get_nth n elements with
566
566
| Some child ->
567
-
Jsont.Array (replace_at n (eval_remove rest child) elements, meta)
567
+
Jsont.Array (replace_at n (eval_remove_segments rest child) elements, meta)
568
568
| None -> error_index_out_of_bounds json n)
569
569
~on_other:(fun () -> error_cannot_navigate json)
570
570
571
-
let remove p json = eval_remove p json
571
+
let remove (p : nav t) json = eval_remove_segments p.segments json
572
572
573
-
(* Mutation: replace *)
573
+
(* Mutation: replace - only for nav pointers *)
574
574
575
-
let replace p json ~value =
576
-
(* Replace requires the target to exist, unlike add *)
575
+
let replace (p : nav t) json ~value =
577
576
let _ = get p json in (* Will raise if not found *)
578
-
eval_set p value json
577
+
eval_set_segments p.segments false value json
579
578
580
579
(* Mutation: move *)
581
580
582
-
let rec is_prefix_of p1 p2 =
583
-
match p1, p2 with
584
-
| [], _ -> true
585
-
| _, [] -> false
586
-
| h1 :: t1, h2 :: t2 -> segment_equal h1 h2 && is_prefix_of t1 t2
587
-
588
-
let move ~from ~path json =
581
+
let move ~(from : nav t) ~(path : _ t) json =
589
582
(* Check for cycle: path cannot be a proper prefix of from *)
590
-
if is_prefix_of path from && not (equal path from) then
583
+
let from_segs = from.segments in
584
+
let path_segs = path.segments in
585
+
let rec is_prefix p1 p2 = match p1, p2 with
586
+
| [], _ -> true
587
+
| _, [] -> false
588
+
| h1 :: t1, h2 :: t2 -> String.equal h1 h2 && is_prefix t1 t2
589
+
in
590
+
if is_prefix path_segs from_segs &&
591
+
not (List.equal String.equal path_segs from_segs && path.is_append = false) then
591
592
Jsont.Error.msgf Jsont.Meta.none
592
593
"JSON Pointer: move would create cycle (path is prefix of from)";
593
594
let value = get from json in
···
596
597
597
598
(* Mutation: copy *)
598
599
599
-
let copy ~from ~path json =
600
+
let copy ~(from : nav t) ~(path : _ t) json =
600
601
let value = get from json in
601
602
add path json ~value
602
603
603
604
(* Mutation: test *)
604
605
605
-
let test p json ~expected =
606
+
let test (p : nav t) json ~expected =
606
607
Option.fold ~none:false ~some:(Jsont.Json.equal expected) (find p json)
607
608
608
609
(* Jsont codec *)
609
610
610
-
let jsont : t Jsont.t =
611
+
let jsont : [ `Nav of nav t | `Append of append t ] Jsont.t =
611
612
let dec _meta s = of_string s in
612
-
let enc p = to_string p in
613
+
let enc = function
614
+
| `Nav p -> to_string p
615
+
| `Append p -> to_string p
616
+
in
613
617
Jsont.Base.string (Jsont.Base.map
614
618
~kind:"JSON Pointer"
615
619
~doc:"RFC 6901 JSON Pointer"
616
620
~dec ~enc ())
617
621
618
-
let jsont_uri_fragment : t Jsont.t =
622
+
let jsont_nav : nav t Jsont.t =
623
+
let dec _meta s = of_string_nav s in
624
+
let enc p = to_string p in
625
+
Jsont.Base.string (Jsont.Base.map
626
+
~kind:"JSON Pointer (nav)"
627
+
~doc:"RFC 6901 JSON Pointer (navigation only)"
628
+
~dec ~enc ())
629
+
630
+
let jsont_uri_fragment : [ `Nav of nav t | `Append of append t ] Jsont.t =
619
631
let dec _meta s = of_uri_fragment s in
620
-
let enc p = to_uri_fragment p in
632
+
let enc = function
633
+
| `Nav p -> to_uri_fragment p
634
+
| `Append p -> to_uri_fragment p
635
+
in
621
636
Jsont.Base.string (Jsont.Base.map
622
637
~kind:"JSON Pointer (URI fragment)"
623
638
~doc:"RFC 6901 JSON Pointer in URI fragment encoding"
···
625
640
626
641
(* Query combinators *)
627
642
628
-
let path ?absent p t =
643
+
let path ?absent (p : nav t) t =
629
644
let dec json =
630
645
match find p json with
631
646
| Some value ->
···
642
657
Jsont.map Jsont.json ~dec ~enc:(fun _ ->
643
658
Jsont.Error.msgf Jsont.Meta.none "path: encode not supported")
644
659
645
-
let set_path ?(allow_absent = false) t p v =
660
+
let set_path (type a) ?(allow_absent = false) t (p : a t) v =
646
661
let encoded = match Jsont.Json.encode' t v with
647
662
| Ok json -> json
648
663
| Error e -> raise (Jsont.Error e)
···
655
670
in
656
671
Jsont.map Jsont.json ~dec ~enc:(fun j -> j)
657
672
658
-
let update_path ?absent p t =
673
+
let update_path ?absent (p : nav t) t =
659
674
let dec json =
660
675
let value = match find p json with
661
676
| Some v -> v
···
681
696
in
682
697
Jsont.map Jsont.json ~dec ~enc:(fun j -> j)
683
698
684
-
let delete_path ?(allow_absent = false) p =
699
+
let delete_path ?(allow_absent = false) (p : nav t) =
685
700
let dec json =
686
701
if allow_absent then
687
702
match find p json with
+167
-116
src/jsont_pointer.mli
+167
-116
src/jsont_pointer.mli
···
9
9
JSON Pointer parsing, serialization, and evaluation compatible with
10
10
{!Jsont} codecs.
11
11
12
-
A JSON Pointer is a string syntax for identifying a specific value within
13
-
a JSON document. For example, given the JSON document:
12
+
{1 JSON Pointer vs JSON Path}
13
+
14
+
JSON Pointer (RFC 6901) and {!Jsont.Path} serve similar purposes but
15
+
have important differences:
16
+
17
+
{ul
18
+
{- {b JSON Pointer} is a {e string syntax} for addressing JSON values,
19
+
designed for use in URIs and JSON documents (like JSON Patch).
20
+
It uses [/] as separator and has escape sequences ([~0], [~1]).}
21
+
{- {b Jsont.Path} is an {e OCaml data structure} for programmatic
22
+
navigation, with no string representation defined.}}
23
+
24
+
A key difference is the [-] token: JSON Pointer's [-] refers to the
25
+
(nonexistent) element {e after} the last array element. This is used
26
+
for append operations in JSON Patch but is meaningless for retrieval.
27
+
{!Jsont.Path} has no equivalent concept.
28
+
29
+
This library uses phantom types to enforce this distinction at compile
30
+
time: pointers that may contain [-] ({!append} pointers) cannot be
31
+
passed to retrieval functions like {!get}.
32
+
33
+
{2 Example}
34
+
35
+
Given the JSON document:
14
36
{v
15
37
{
16
38
"foo": ["bar", "baz"],
···
27
49
{- ["/foo/0"] - the string ["bar"]}
28
50
{- ["/"] - the integer [0] (empty string key)}
29
51
{- ["/a~1b"] - the integer [1] ([~1] escapes [/])}
30
-
{- ["/m~0n"] - the integer [2] ([~0] escapes [~])}}
52
+
{- ["/m~0n"] - the integer [2] ([~0] escapes [~])}
53
+
{- ["/foo/-"] - nonexistent; only valid for mutations}}
31
54
32
55
{1:tokens Reference Tokens}
33
56
···
60
83
61
84
(** {1 Indices}
62
85
63
-
Indices represent individual navigation steps in a JSON Pointer.
64
-
For objects, this is a member name. For arrays, this is either
65
-
a numeric index or the special end-of-array marker [-]. *)
66
-
module Index : sig
86
+
Indices are the individual navigation steps in a JSON Pointer.
87
+
This library reuses {!Jsont.Path.index} directly - the JSON Pointer
88
+
specific [-] token is handled separately via phantom types on the
89
+
pointer type itself. *)
67
90
68
-
type t = [
69
-
| `Mem of string
70
-
(** [`Mem name] indexes into an object member with the given [name].
71
-
The name is unescaped (i.e., [/] and [~] appear literally). *)
72
-
| `Nth of int
73
-
(** [`Nth n] indexes into an array at position [n] (zero-based).
74
-
Must be non-negative and without leading zeros in string form
75
-
(except for [0] itself). *)
76
-
| `End
77
-
(** [`End] represents the [-] token, indicating the position after
78
-
the last element of an array. This is used for append operations
79
-
in {!Jsont_pointer.add} and similar mutation functions.
80
-
Evaluating a pointer containing [`End] with {!Jsont_pointer.get}
81
-
will raise an error since it refers to a nonexistent element. *)
82
-
]
91
+
type index = Jsont.Path.index
92
+
(** The type for navigation indices. This is exactly {!Jsont.Path.index}:
93
+
either [Jsont.Path.Mem (name, meta)] for object member access or
94
+
[Jsont.Path.Nth (n, meta)] for array index access. *)
83
95
84
-
val pp : Format.formatter -> t -> unit
85
-
(** [pp] formats an index in JSON Pointer string notation. *)
96
+
val mem : ?meta:Jsont.Meta.t -> string -> index
97
+
(** [mem ?meta s] is [Jsont.Path.Mem (s, meta)].
98
+
Convenience constructor for object member access.
99
+
[meta] defaults to {!Jsont.Meta.none}. *)
86
100
87
-
val equal : t -> t -> bool
88
-
(** [equal i1 i2] is [true] iff [i1] and [i2] are the same index. *)
101
+
val nth : ?meta:Jsont.Meta.t -> int -> index
102
+
(** [nth ?meta n] is [Jsont.Path.Nth (n, meta)].
103
+
Convenience constructor for array index access.
104
+
[meta] defaults to {!Jsont.Meta.none}. *)
89
105
90
-
val compare : t -> t -> int
91
-
(** [compare i1 i2] is a total order on indices. *)
106
+
val pp_index : Format.formatter -> index -> unit
107
+
(** [pp_index] formats an index in JSON Pointer string notation. *)
92
108
93
-
(** {2:jsont_conv Conversion with Jsont.Path} *)
109
+
val equal_index : index -> index -> bool
110
+
(** [equal_index i1 i2] is [true] iff [i1] and [i2] are the same index. *)
94
111
95
-
val of_path_index : Jsont.Path.index -> t
96
-
(** [of_path_index idx] converts a {!Jsont.Path.index} to an index. *)
112
+
val compare_index : index -> index -> int
113
+
(** [compare_index i1 i2] is a total order on indices. *)
97
114
98
-
val to_path_index : t -> Jsont.Path.index option
99
-
(** [to_path_index idx] converts to a {!Jsont.Path.index}.
100
-
Returns [None] for [`End] since it has no equivalent in
101
-
{!Jsont.Path}. *)
102
-
end
115
+
(** {1 Pointers}
103
116
104
-
(** {1 Pointers} *)
117
+
JSON Pointers use phantom types to distinguish between:
118
+
{ul
119
+
{- {!nav} pointers that reference existing elements (safe for all operations)}
120
+
{- {!append} pointers that end with [-] (only valid for {!add} and {!set})}}
105
121
106
-
type t
107
-
(** The type for JSON Pointers. A pointer is a sequence of {!Index.t}
108
-
values representing a path from the root of a JSON document to
109
-
a specific value. *)
122
+
This ensures at compile time that you cannot accidentally try to
123
+
retrieve a nonexistent "end of array" position. *)
124
+
125
+
type 'a t
126
+
(** The type for JSON Pointers. The phantom type ['a] indicates whether
127
+
the pointer can be used for navigation ([nav]) or only for append
128
+
operations ([append]). *)
129
+
130
+
type nav
131
+
(** Phantom type for pointers that reference existing elements.
132
+
These can be used with all operations including {!get} and {!find}. *)
133
+
134
+
type append
135
+
(** Phantom type for pointers ending with [-] (the "after last element"
136
+
position). These can only be used with {!add} and {!set}. *)
110
137
111
-
val root : t
138
+
val root : nav t
112
139
(** [root] is the empty pointer that references the whole document.
113
140
In string form this is [""]. *)
114
141
115
-
val is_root : t -> bool
142
+
val is_root : _ t -> bool
116
143
(** [is_root p] is [true] iff [p] is the {!root} pointer. *)
117
144
118
-
val make : Index.t list -> t
119
-
(** [make indices] creates a pointer from a list of indices.
145
+
val make : index list -> nav t
146
+
(** [make indices] creates a navigation pointer from a list of indices.
120
147
The list is ordered from root to target (i.e., the first element
121
148
is the first step from the root). *)
122
149
123
-
val indices : t -> Index.t list
124
-
(** [indices p] returns the indices of [p] from root to target. *)
150
+
val ( / ) : nav t -> index -> nav t
151
+
(** [p / idx] appends [idx] to pointer [p]. Operator form of {!append_index}. *)
152
+
153
+
val append_index : nav t -> index -> nav t
154
+
(** [append_index p idx] appends [idx] to the end of pointer [p]. *)
125
155
126
-
val append : t -> Index.t -> t
127
-
(** [append p idx] appends [idx] to the end of pointer [p]. *)
156
+
val at_end : nav t -> append t
157
+
(** [at_end p] creates an append pointer by adding [-] to [p].
158
+
The resulting pointer refers to the position after the last element
159
+
of the array at [p]. Only valid for use with {!add} and {!set}. *)
128
160
129
-
val concat : t -> t -> t
161
+
val concat : nav t -> nav t -> nav t
130
162
(** [concat p1 p2] appends all indices of [p2] to [p1]. *)
131
163
132
-
val parent : t -> t option
164
+
val parent : nav t -> nav t option
133
165
(** [parent p] returns the parent pointer of [p], or [None] if [p]
134
166
is the {!root}. *)
135
167
136
-
val last : t -> Index.t option
168
+
val last : nav t -> index option
137
169
(** [last p] returns the last index of [p], or [None] if [p] is
138
170
the {!root}. *)
139
171
172
+
val indices : _ t -> index list
173
+
(** [indices p] returns the indices of [p] from root to target.
174
+
Note: for append pointers, this returns the indices of the path
175
+
portion; the [-] (append position) is not represented as an index. *)
176
+
140
177
(** {2:parsing Parsing} *)
141
178
142
-
val of_string : string -> t
179
+
val of_string : string -> [ `Nav of nav t | `Append of append t ]
143
180
(** [of_string s] parses a JSON Pointer from its string representation.
144
181
182
+
Returns [`Nav p] for pointers without [-], or [`Append p] for
183
+
pointers ending with [-].
184
+
145
185
The string must be either empty (representing the root) or start
146
186
with [/]. Each segment between [/] characters is unescaped as a
147
-
reference token. Segments that are valid non-negative integers
148
-
without leading zeros become [`Nth] indices; the string [-]
149
-
becomes [`End]; all others become [`Mem].
187
+
reference token.
150
188
151
189
@raise Jsont.Error if [s] has invalid syntax:
152
190
- Non-empty string not starting with [/]
153
191
- Invalid escape sequence ([~] not followed by [0] or [1])
154
-
- Array index with leading zeros
155
-
- Array index that overflows [int] *)
192
+
- [-] appears in non-final position *)
193
+
194
+
val of_string_nav : string -> nav t
195
+
(** [of_string_nav s] parses a JSON Pointer that must not contain [-].
196
+
197
+
@raise Jsont.Error if [s] has invalid syntax or contains [-]. *)
156
198
157
-
val of_string_result : string -> (t, string) result
199
+
val of_string_result : string -> ([ `Nav of nav t | `Append of append t ], string) result
158
200
(** [of_string_result s] is like {!of_string} but returns a result
159
201
instead of raising. *)
160
202
161
-
val of_uri_fragment : string -> t
203
+
val of_uri_fragment : string -> [ `Nav of nav t | `Append of append t ]
162
204
(** [of_uri_fragment s] parses a JSON Pointer from URI fragment form.
163
205
164
206
This is like {!of_string} but first percent-decodes the string
···
167
209
168
210
@raise Jsont.Error on invalid syntax or invalid percent-encoding. *)
169
211
170
-
val of_uri_fragment_result : string -> (t, string) result
212
+
val of_uri_fragment_nav : string -> nav t
213
+
(** [of_uri_fragment_nav s] is like {!of_uri_fragment} but requires
214
+
the pointer to not contain [-].
215
+
216
+
@raise Jsont.Error if invalid or contains [-]. *)
217
+
218
+
val of_uri_fragment_result : string -> ([ `Nav of nav t | `Append of append t ], string) result
171
219
(** [of_uri_fragment_result s] is like {!of_uri_fragment} but returns
172
220
a result instead of raising. *)
173
221
174
222
(** {2:serializing Serializing} *)
175
223
176
-
val to_string : t -> string
224
+
val to_string : _ t -> string
177
225
(** [to_string p] serializes [p] to its JSON Pointer string representation.
178
226
179
227
Returns [""] for the root pointer, otherwise [/] followed by
180
-
escaped reference tokens joined by [/]. *)
228
+
escaped reference tokens joined by [/]. Append pointers include
229
+
the trailing [/-]. *)
181
230
182
-
val to_uri_fragment : t -> string
231
+
val to_uri_fragment : _ t -> string
183
232
(** [to_uri_fragment p] serializes [p] to URI fragment form.
184
233
185
234
This is like {!to_string} but additionally percent-encodes
186
235
characters that are not allowed in URI fragments per RFC 3986.
187
236
The leading [#] is {b not} included in the result. *)
188
237
189
-
val pp : Format.formatter -> t -> unit
238
+
val pp : Format.formatter -> _ t -> unit
190
239
(** [pp] formats a pointer using {!to_string}. *)
191
240
192
-
val pp_verbose : Format.formatter -> t -> unit
241
+
val pp_verbose : Format.formatter -> _ t -> unit
193
242
(** [pp_verbose] formats a pointer showing its index structure.
194
-
For example, [/foo/0/-] is formatted as [[`Mem "foo"; `Nth 0; `End]].
243
+
For example, [/foo/0] is formatted as [[Mem "foo"; Nth 0]].
244
+
Append pointers show [/-] at the end.
195
245
Useful for debugging and understanding pointer structure. *)
196
246
197
247
(** {2:comparison Comparison} *)
198
248
199
-
val equal : t -> t -> bool
200
-
(** [equal p1 p2] is [true] iff [p1] and [p2] have the same indices. *)
249
+
val equal : _ t -> _ t -> bool
250
+
(** [equal p1 p2] is [true] iff [p1] and [p2] have the same indices
251
+
and the same append status. *)
201
252
202
-
val compare : t -> t -> int
253
+
val compare : _ t -> _ t -> int
203
254
(** [compare p1 p2] is a total order on pointers, comparing indices
204
-
lexicographically. *)
255
+
lexicographically. Append pointers sort after nav pointers with
256
+
the same prefix. *)
205
257
206
258
(** {2:jsont_path Conversion with Jsont.Path} *)
207
259
208
-
val of_path : Jsont.Path.t -> t
209
-
(** [of_path p] converts a {!Jsont.Path.t} to a JSON Pointer. *)
260
+
val of_path : Jsont.Path.t -> nav t
261
+
(** [of_path p] converts a {!Jsont.Path.t} to a JSON Pointer.
262
+
Always returns a {!nav} pointer since {!Jsont.Path} has no [-] concept. *)
210
263
211
-
val to_path : t -> Jsont.Path.t option
212
-
(** [to_path p] converts to a {!Jsont.Path.t}.
213
-
Returns [None] if [p] contains an [`End] index. *)
214
-
215
-
val to_path_exn : t -> Jsont.Path.t
216
-
(** [to_path_exn p] is like {!to_path} but raises {!Jsont.Error}
217
-
if conversion fails. *)
264
+
val to_path : nav t -> Jsont.Path.t
265
+
(** [to_path p] converts a navigation pointer to a {!Jsont.Path.t}. *)
218
266
219
267
(** {1 Evaluation}
220
268
221
269
These functions evaluate a JSON Pointer against a {!Jsont.json} value
222
-
to retrieve the referenced value. *)
270
+
to retrieve the referenced value. They only accept {!nav} pointers
271
+
since {!append} pointers refer to nonexistent positions. *)
223
272
224
-
val get : t -> Jsont.json -> Jsont.json
273
+
val get : nav t -> Jsont.json -> Jsont.json
225
274
(** [get p json] retrieves the value at pointer [p] in [json].
226
275
227
276
@raise Jsont.Error if:
228
277
- The pointer references a nonexistent object member
229
278
- The pointer references an out-of-bounds array index
230
-
- The pointer contains [`End] (since [-] always refers
231
-
to a nonexistent element)
232
-
- An index type doesn't match the JSON value (e.g., [`Nth]
279
+
- An index type doesn't match the JSON value (e.g., [Nth]
233
280
on an object) *)
234
281
235
-
val get_result : t -> Jsont.json -> (Jsont.json, Jsont.Error.t) result
282
+
val get_result : nav t -> Jsont.json -> (Jsont.json, Jsont.Error.t) result
236
283
(** [get_result p json] is like {!get} but returns a result. *)
237
284
238
-
val find : t -> Jsont.json -> Jsont.json option
285
+
val find : nav t -> Jsont.json -> Jsont.json option
239
286
(** [find p json] is like {!get} but returns [None] instead of
240
287
raising when the pointer doesn't resolve to a value. *)
241
288
···
247
294
operations.
248
295
249
296
All mutation functions return a new JSON value with the modification
250
-
applied; they do not mutate the input. *)
297
+
applied; they do not mutate the input.
298
+
299
+
Functions that support the [-] token ({!add}, {!set}) accept any
300
+
pointer type ([_ t]). Functions that require an existing element
301
+
({!remove}, {!replace}) only accept {!nav} pointers. *)
251
302
252
-
val set : t -> Jsont.json -> value:Jsont.json -> Jsont.json
303
+
val set : _ t -> Jsont.json -> value:Jsont.json -> Jsont.json
253
304
(** [set p json ~value] replaces the value at pointer [p] with [value].
254
305
255
-
For [`End] on arrays, appends [value] to the end of the array.
306
+
For {!append} pointers, appends [value] to the end of the array.
256
307
257
308
@raise Jsont.Error if the pointer doesn't resolve to an existing
258
-
location (except for [`End] on arrays). *)
309
+
location (except for {!append} pointers on arrays). *)
259
310
260
-
val add : t -> Jsont.json -> value:Jsont.json -> Jsont.json
311
+
val add : _ t -> Jsont.json -> value:Jsont.json -> Jsont.json
261
312
(** [add p json ~value] adds [value] at the location specified by [p].
262
313
263
314
The behavior depends on the target:
264
315
{ul
265
316
{- For objects: If the member exists, it is replaced. If it doesn't
266
317
exist, a new member is added.}
267
-
{- For arrays with [`Nth]: Inserts [value] {e before} the
318
+
{- For arrays with [Nth]: Inserts [value] {e before} the
268
319
specified index, shifting subsequent elements. The index must be
269
320
valid (0 to length inclusive).}
270
-
{- For arrays with [`End]: Appends [value] to the array.}}
321
+
{- For {!append} pointers: Appends [value] to the array.}}
271
322
272
323
@raise Jsont.Error if:
273
324
- The parent of the target location doesn't exist
274
-
- An array index is out of bounds (except for [`End])
325
+
- An array index is out of bounds (except for {!append} pointers)
275
326
- The parent is not an object or array *)
276
327
277
-
val remove : t -> Jsont.json -> Jsont.json
328
+
val remove : nav t -> Jsont.json -> Jsont.json
278
329
(** [remove p json] removes the value at pointer [p].
279
330
280
331
For objects, removes the member. For arrays, removes the element
···
282
333
283
334
@raise Jsont.Error if:
284
335
- [p] is the root (cannot remove the root)
285
-
- The pointer doesn't resolve to an existing value
286
-
- The pointer contains [`End] *)
336
+
- The pointer doesn't resolve to an existing value *)
287
337
288
-
val replace : t -> Jsont.json -> value:Jsont.json -> Jsont.json
338
+
val replace : nav t -> Jsont.json -> value:Jsont.json -> Jsont.json
289
339
(** [replace p json ~value] replaces the value at pointer [p] with [value].
290
340
291
341
Unlike {!add}, this requires the target to exist.
292
342
293
-
@raise Jsont.Error if:
294
-
- The pointer doesn't resolve to an existing value
295
-
- The pointer contains [`End] *)
343
+
@raise Jsont.Error if the pointer doesn't resolve to an existing value. *)
296
344
297
-
val move : from:t -> path:t -> Jsont.json -> Jsont.json
345
+
val move : from:nav t -> path:_ t -> Jsont.json -> Jsont.json
298
346
(** [move ~from ~path json] moves the value from [from] to [path].
299
347
300
348
This is equivalent to {!remove} at [from] followed by {!add}
···
302
350
303
351
@raise Jsont.Error if:
304
352
- [from] doesn't resolve to a value
305
-
- [path] is a proper prefix of [from] (would create a cycle)
306
-
- Either pointer contains [`End] *)
353
+
- [path] is a proper prefix of [from] (would create a cycle) *)
307
354
308
-
val copy : from:t -> path:t -> Jsont.json -> Jsont.json
355
+
val copy : from:nav t -> path:_ t -> Jsont.json -> Jsont.json
309
356
(** [copy ~from ~path json] copies the value from [from] to [path].
310
357
311
358
This is equivalent to {!get} at [from] followed by {!add}
312
359
at [path] with the retrieved value.
313
360
314
-
@raise Jsont.Error if:
315
-
- [from] doesn't resolve to a value
316
-
- Either pointer contains [`End] *)
361
+
@raise Jsont.Error if [from] doesn't resolve to a value. *)
317
362
318
-
val test : t -> Jsont.json -> expected:Jsont.json -> bool
363
+
val test : nav t -> Jsont.json -> expected:Jsont.json -> bool
319
364
(** [test p json ~expected] tests if the value at [p] equals [expected].
320
365
321
366
Returns [true] if the values are equal according to {!Jsont.Json.equal},
···
329
374
These types and functions integrate JSON Pointers with the {!Jsont}
330
375
codec system. *)
331
376
332
-
val jsont : t Jsont.t
377
+
val jsont : [ `Nav of nav t | `Append of append t ] Jsont.t
333
378
(** [jsont] is a {!Jsont.t} codec for JSON Pointers.
334
379
335
380
On decode, parses a JSON string as a JSON Pointer using {!of_string}.
336
381
On encode, serializes a pointer to a JSON string using {!to_string}. *)
337
382
338
-
val jsont_uri_fragment : t Jsont.t
383
+
val jsont_nav : nav t Jsont.t
384
+
(** [jsont_nav] is a {!Jsont.t} codec for navigation JSON Pointers.
385
+
386
+
On decode, parses using {!of_string_nav} (fails on [-]).
387
+
On encode, serializes using {!to_string}. *)
388
+
389
+
val jsont_uri_fragment : [ `Nav of nav t | `Append of append t ] Jsont.t
339
390
(** [jsont_uri_fragment] is like {!jsont} but uses URI fragment encoding.
340
391
341
392
On decode, parses using {!of_uri_fragment}.
···
346
397
These combinators integrate with jsont's query system, allowing
347
398
JSON Pointers to be used with jsont codecs for typed access. *)
348
399
349
-
val path : ?absent:'a -> t -> 'a Jsont.t -> 'a Jsont.t
400
+
val path : ?absent:'a -> nav t -> 'a Jsont.t -> 'a Jsont.t
350
401
(** [path p t] decodes the value at pointer [p] using codec [t].
351
402
352
403
If [absent] is provided and the pointer doesn't resolve, returns
···
354
405
355
406
This is similar to {!Jsont.path} but uses JSON Pointer syntax. *)
356
407
357
-
val set_path : ?allow_absent:bool -> 'a Jsont.t -> t -> 'a -> Jsont.json Jsont.t
408
+
val set_path : ?allow_absent:bool -> 'a Jsont.t -> _ t -> 'a -> Jsont.json Jsont.t
358
409
(** [set_path t p v] sets the value at pointer [p] to [v] encoded with [t].
359
410
360
411
If [allow_absent] is [true] (default [false]), creates missing
···
362
413
363
414
This is similar to {!Jsont.set_path} but uses JSON Pointer syntax. *)
364
415
365
-
val update_path : ?absent:'a -> t -> 'a Jsont.t -> Jsont.json Jsont.t
416
+
val update_path : ?absent:'a -> nav t -> 'a Jsont.t -> Jsont.json Jsont.t
366
417
(** [update_path p t] recodes the value at pointer [p] with codec [t].
367
418
368
419
This is similar to {!Jsont.update_path} but uses JSON Pointer syntax. *)
369
420
370
-
val delete_path : ?allow_absent:bool -> t -> Jsont.json Jsont.t
421
+
val delete_path : ?allow_absent:bool -> nav t -> Jsont.json Jsont.t
371
422
(** [delete_path p] removes the value at pointer [p].
372
423
373
424
If [allow_absent] is [true] (default [false]), does nothing if
+4
-4
test/comprehensive.t
+4
-4
test/comprehensive.t
···
4
4
$ ./test_pointer.exe parse "/foo/bar"
5
5
OK: [Mem:foo, Mem:bar]
6
6
$ ./test_pointer.exe parse "/foo/-"
7
-
OK: [Mem:foo, End]
7
+
OK: [Mem:foo, /-]
8
8
$ ./test_pointer.exe parse "/foo/1"
9
9
OK: [Mem:foo, Nth:1]
10
10
$ ./test_pointer.exe parse "/foo/~0"
···
144
144
$ ./test_pointer.exe has 'null' ''
145
145
true
146
146
147
-
Has with '-' (end marker - should be false for get, points to nonexistent):
147
+
Has with '-' (end marker - now errors because has uses of_string_nav):
148
148
$ ./test_pointer.exe has '["foo"]' '/-'
149
-
false
149
+
ERROR: Invalid JSON Pointer: '-' not allowed in navigation pointer
150
150
$ ./test_pointer.exe has '[]' '/-'
151
-
false
151
+
ERROR: Invalid JSON Pointer: '-' not allowed in navigation pointer
+1
-2
test/eval.t
+1
-2
test/eval.t
···
65
65
66
66
Error: end marker not allowed in get:
67
67
$ ./test_pointer.exe eval data/rfc6901_example.json "/foo/-"
68
-
ERROR: JSON Pointer: '-' (end marker) refers to nonexistent array element
69
-
File "-":
68
+
ERROR: Invalid JSON Pointer: '-' not allowed in navigation pointer
70
69
71
70
Error: navigating through primitive (string):
72
71
$ ./test_pointer.exe eval data/rfc6901_example.json "/foo/0/0"
+2
-2
test/parse.t
+2
-2
test/parse.t
+48
-25
test/test_pointer.ml
+48
-25
test/test_pointer.ml
···
17
17
| Ok s -> s
18
18
| Error e -> failwith e
19
19
20
+
(* Helper to get indices from either nav or append pointer *)
21
+
let indices_of_result (result : [ `Nav of Jsont_pointer.nav Jsont_pointer.t
22
+
| `Append of Jsont_pointer.append Jsont_pointer.t ]) =
23
+
match result with
24
+
| `Nav p -> Jsont_pointer.indices p
25
+
| `Append p -> Jsont_pointer.indices p
26
+
27
+
(* Helper to convert to string from either nav or append pointer *)
28
+
let to_string_of_result (result : [ `Nav of Jsont_pointer.nav Jsont_pointer.t
29
+
| `Append of Jsont_pointer.append Jsont_pointer.t ]) =
30
+
match result with
31
+
| `Nav p -> Jsont_pointer.to_string p
32
+
| `Append p -> Jsont_pointer.to_string p
33
+
20
34
(* Test: parse pointer and print indices *)
21
35
let test_parse pointer_str =
22
36
try
23
-
let p = Jsont_pointer.of_string pointer_str in
24
-
let indices = Jsont_pointer.indices p in
37
+
let result = Jsont_pointer.of_string pointer_str in
38
+
let indices = indices_of_result result in
25
39
let index_strs = List.map (fun idx ->
26
40
match idx with
27
-
| `Mem s -> Printf.sprintf "Mem:%s" s
28
-
| `Nth n -> Printf.sprintf "Nth:%d" n
29
-
| `End -> "End"
41
+
| Jsont.Path.Mem (s, _) -> Printf.sprintf "Mem:%s" s
42
+
| Jsont.Path.Nth (n, _) -> Printf.sprintf "Nth:%d" n
30
43
) indices in
31
-
Printf.printf "OK: [%s]\n" (String.concat ", " index_strs)
44
+
let suffix = match result with `Nav _ -> "" | `Append _ -> ", /-" in
45
+
Printf.printf "OK: [%s%s]\n" (String.concat ", " index_strs) suffix
32
46
with Jsont.Error e ->
33
47
Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e)
34
48
35
49
(* Test: roundtrip pointer string *)
36
50
let test_roundtrip pointer_str =
37
51
try
38
-
let p = Jsont_pointer.of_string pointer_str in
39
-
let s = Jsont_pointer.to_string p in
52
+
let result = Jsont_pointer.of_string pointer_str in
53
+
let s = to_string_of_result result in
40
54
if s = pointer_str then
41
55
Printf.printf "OK: %s\n" s
42
56
else
···
48
62
let test_eval json_path pointer_str =
49
63
try
50
64
let json = parse_json (read_file json_path) in
51
-
let p = Jsont_pointer.of_string pointer_str in
65
+
let p = Jsont_pointer.of_string_nav pointer_str in
52
66
let result = Jsont_pointer.get p json in
53
67
Printf.printf "OK: %s\n" (json_to_string result)
54
68
with
···
73
87
(* Test: URI fragment roundtrip *)
74
88
let test_uri_fragment pointer_str =
75
89
try
76
-
let p = Jsont_pointer.of_string pointer_str in
77
-
let frag = Jsont_pointer.to_uri_fragment p in
78
-
let p2 = Jsont_pointer.of_uri_fragment frag in
79
-
let s2 = Jsont_pointer.to_string p2 in
90
+
let result = Jsont_pointer.of_string pointer_str in
91
+
let frag = match result with
92
+
| `Nav p -> Jsont_pointer.to_uri_fragment p
93
+
| `Append p -> Jsont_pointer.to_uri_fragment p
94
+
in
95
+
let result2 = Jsont_pointer.of_uri_fragment frag in
96
+
let s2 = to_string_of_result result2 in
80
97
if s2 = pointer_str then
81
98
Printf.printf "OK: %s -> %s\n" pointer_str frag
82
99
else
···
88
105
let test_add json_str pointer_str value_str =
89
106
try
90
107
let json = parse_json json_str in
91
-
let p = Jsont_pointer.of_string pointer_str in
92
108
let value = parse_json value_str in
93
-
let result = Jsont_pointer.add p json ~value in
109
+
let result = match Jsont_pointer.of_string pointer_str with
110
+
| `Nav p -> Jsont_pointer.add p json ~value
111
+
| `Append p -> Jsont_pointer.add p json ~value
112
+
in
94
113
Printf.printf "%s\n" (json_to_string result)
95
114
with Jsont.Error e ->
96
115
Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e)
···
99
118
let test_remove json_str pointer_str =
100
119
try
101
120
let json = parse_json json_str in
102
-
let p = Jsont_pointer.of_string pointer_str in
121
+
let p = Jsont_pointer.of_string_nav pointer_str in
103
122
let result = Jsont_pointer.remove p json in
104
123
Printf.printf "%s\n" (json_to_string result)
105
124
with Jsont.Error e ->
···
109
128
let test_replace json_str pointer_str value_str =
110
129
try
111
130
let json = parse_json json_str in
112
-
let p = Jsont_pointer.of_string pointer_str in
131
+
let p = Jsont_pointer.of_string_nav pointer_str in
113
132
let value = parse_json value_str in
114
133
let result = Jsont_pointer.replace p json ~value in
115
134
Printf.printf "%s\n" (json_to_string result)
···
120
139
let test_move json_str from_str path_str =
121
140
try
122
141
let json = parse_json json_str in
123
-
let from = Jsont_pointer.of_string from_str in
124
-
let path = Jsont_pointer.of_string path_str in
125
-
let result = Jsont_pointer.move ~from ~path json in
142
+
let from = Jsont_pointer.of_string_nav from_str in
143
+
let result = match Jsont_pointer.of_string path_str with
144
+
| `Nav path -> Jsont_pointer.move ~from ~path json
145
+
| `Append path -> Jsont_pointer.move ~from ~path json
146
+
in
126
147
Printf.printf "%s\n" (json_to_string result)
127
148
with Jsont.Error e ->
128
149
Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e)
···
131
152
let test_copy json_str from_str path_str =
132
153
try
133
154
let json = parse_json json_str in
134
-
let from = Jsont_pointer.of_string from_str in
135
-
let path = Jsont_pointer.of_string path_str in
136
-
let result = Jsont_pointer.copy ~from ~path json in
155
+
let from = Jsont_pointer.of_string_nav from_str in
156
+
let result = match Jsont_pointer.of_string path_str with
157
+
| `Nav path -> Jsont_pointer.copy ~from ~path json
158
+
| `Append path -> Jsont_pointer.copy ~from ~path json
159
+
in
137
160
Printf.printf "%s\n" (json_to_string result)
138
161
with Jsont.Error e ->
139
162
Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e)
···
142
165
let test_test json_str pointer_str expected_str =
143
166
try
144
167
let json = parse_json json_str in
145
-
let p = Jsont_pointer.of_string pointer_str in
168
+
let p = Jsont_pointer.of_string_nav pointer_str in
146
169
let expected = parse_json expected_str in
147
170
let result = Jsont_pointer.test p json ~expected in
148
171
Printf.printf "%b\n" result
···
153
176
let test_has json_str pointer_str =
154
177
try
155
178
let json = parse_json json_str in
156
-
let p = Jsont_pointer.of_string pointer_str in
179
+
let p = Jsont_pointer.of_string_nav pointer_str in
157
180
let result = Jsont_pointer.find p json in
158
181
Printf.printf "%b\n" (Option.is_some result)
159
182
with Jsont.Error e ->