+3
-3
bin/jsonpp.ml
+3
-3
bin/jsonpp.ml
···
24
24
let indices = Jsont_pointer.indices p in
25
25
let index_strs = List.map (fun idx ->
26
26
match idx with
27
-
| Jsont_pointer.Index.Mem s -> Printf.sprintf "Mem:%s" s
28
-
| Jsont_pointer.Index.Nth n -> Printf.sprintf "Nth:%d" n
29
-
| Jsont_pointer.Index.End -> "End"
27
+
| `Mem s -> Printf.sprintf "Mem:%s" s
28
+
| `Nth n -> Printf.sprintf "Nth:%d" n
29
+
| `End -> "End"
30
30
) indices in
31
31
Printf.printf "OK: [%s]\n" (String.concat ", " index_strs)
32
32
with Jsont.Error e ->
-4
doc/dune
-4
doc/dune
+335
-474
doc/tutorial.md
+335
-474
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
+
## Setup
8
+
9
+
First, let's set up our environment with helper functions:
10
+
11
+
```ocaml
12
+
# open Jsont_pointer;;
13
+
# let parse_json s =
14
+
match Jsont_bytesrw.decode_string Jsont.json s with
15
+
| Ok json -> json
16
+
| Error e -> failwith e;;
17
+
val parse_json : string -> Jsont.json = <fun>
18
+
# let json_to_string json =
19
+
match Jsont_bytesrw.encode_string ~format:Jsont.Minify Jsont.json json with
20
+
| Ok s -> s
21
+
| Error e -> failwith e;;
22
+
val json_to_string : Jsont.json -> string = <fun>
23
+
# let show_pointer p =
24
+
let idxs = indices p in
25
+
let show_index = function
26
+
| `Mem s -> "Mem:" ^ s
27
+
| `Nth n -> "Nth:" ^ string_of_int n
28
+
| `End -> "End"
29
+
in
30
+
"[" ^ String.concat ", " (List.map show_index idxs) ^ "]";;
31
+
val show_pointer : t -> string = <fun>
32
+
```
33
+
7
34
## What is JSON Pointer?
8
35
9
36
From RFC 6901, Section 1:
···
17
44
18
45
For example, given this JSON document:
19
46
20
-
```json
21
-
{
22
-
"users": [
23
-
{"name": "Alice", "age": 30},
24
-
{"name": "Bob", "age": 25}
25
-
]
26
-
}
47
+
```ocaml
48
+
# let users_json = parse_json {|{
49
+
"users": [
50
+
{"name": "Alice", "age": 30},
51
+
{"name": "Bob", "age": 25}
52
+
]
53
+
}|};;
54
+
val users_json : Jsont.json =
55
+
Jsont.Object
56
+
([(("users", <abstr>),
57
+
Jsont.Array
58
+
([Jsont.Object
59
+
([(("name", <abstr>), Jsont.String ("Alice", <abstr>));
60
+
(("age", <abstr>), Jsont.Number (30., <abstr>))],
61
+
<abstr>);
62
+
Jsont.Object
63
+
([(("name", <abstr>), Jsont.String ("Bob", <abstr>));
64
+
(("age", <abstr>), Jsont.Number (25., <abstr>))],
65
+
<abstr>)],
66
+
<abstr>))],
67
+
<abstr>)
27
68
```
28
69
29
-
The JSON Pointer `/users/0/name` refers to the string `"Alice"`.
70
+
The JSON Pointer `/users/0/name` refers to the string `"Alice"`:
71
+
72
+
```ocaml
73
+
# let ptr = of_string "/users/0/name";;
74
+
val ptr : t = <abstr>
75
+
# get ptr users_json;;
76
+
- : Jsont.json = Jsont.String ("Alice", <abstr>)
77
+
```
30
78
31
79
In OCaml, this is represented by the `Jsont_pointer.t` type - a sequence
32
80
of navigation steps from the document root to a target value.
···
50
98
- Every non-empty pointer starts with `/`
51
99
- Everything between `/` characters is a "reference token"
52
100
53
-
Let's see this in action. We can parse pointers and see their structure:
101
+
Let's see this in action:
54
102
55
-
```sh
56
-
$ jsonpp parse ""
57
-
OK: []
103
+
```ocaml
104
+
# let empty_ptr = of_string "";;
105
+
val empty_ptr : t = <abstr>
106
+
# show_pointer empty_ptr;;
107
+
- : string = "[]"
58
108
```
59
109
60
110
The empty pointer has no reference tokens - it points to the root.
61
111
62
-
```sh
63
-
$ jsonpp parse "/foo"
64
-
OK: [Mem:foo]
112
+
```ocaml
113
+
# let foo_ptr = of_string "/foo";;
114
+
val foo_ptr : t = <abstr>
115
+
# show_pointer foo_ptr;;
116
+
- : string = "[Mem:foo]"
65
117
```
66
118
67
119
The pointer `/foo` has one token: `foo`. Since it's not a number, it's
68
120
interpreted as an object member name (`Mem`).
69
121
70
-
```sh
71
-
$ jsonpp parse "/foo/0"
72
-
OK: [Mem:foo, Nth:0]
122
+
```ocaml
123
+
# let foo_0_ptr = of_string "/foo/0";;
124
+
val foo_0_ptr : t = <abstr>
125
+
# show_pointer foo_0_ptr;;
126
+
- : string = "[Mem:foo, Nth:0]"
73
127
```
74
128
75
129
Here we have two tokens: `foo` (a member name) and `0` (interpreted as
76
130
an array index `Nth`).
77
131
78
-
```sh
79
-
$ jsonpp parse "/foo/bar/baz"
80
-
OK: [Mem:foo, Mem:bar, Mem:baz]
132
+
```ocaml
133
+
# show_pointer (of_string "/foo/bar/baz");;
134
+
- : string = "[Mem:foo, Mem:bar, Mem:baz]"
81
135
```
82
136
83
137
Multiple tokens navigate deeper into nested structures.
···
88
142
89
143
<!-- $MDX skip -->
90
144
```ocaml
91
-
type t =
92
-
| Mem of string (* Object member access *)
93
-
| Nth of int (* Array index access *)
94
-
| End (* The special "-" marker for append operations *)
145
+
type t = [
146
+
| `Mem of string (* Object member access *)
147
+
| `Nth of int (* Array index access *)
148
+
| `End (* The special "-" marker for append operations *)
149
+
]
95
150
```
96
151
97
152
The `Mem` variant holds the **unescaped** member name - you work with the
···
102
157
103
158
What happens if a pointer doesn't start with `/`?
104
159
105
-
```sh
106
-
$ jsonpp parse "foo"
107
-
ERROR: Invalid JSON Pointer: must be empty or start with '/': foo
160
+
```ocaml
161
+
# of_string "foo";;
162
+
Exception: Jsont.Error ([], <abstr>, <abstr>).
108
163
```
109
164
110
165
The RFC is strict: non-empty pointers MUST start with `/`.
111
166
167
+
For safer parsing, use `of_string_result`:
168
+
169
+
```ocaml
170
+
# of_string_result "foo";;
171
+
- : (t, string) result =
172
+
Error "Invalid JSON Pointer: must be empty or start with '/': foo"
173
+
# of_string_result "/valid";;
174
+
- : (t, string) result = Ok <abstr>
175
+
```
176
+
112
177
## Evaluation: Navigating JSON
113
178
114
179
Now we come to the heart of JSON Pointer: evaluation. RFC 6901, Section 4
···
119
184
> the document. Each reference token in the JSON Pointer is evaluated
120
185
> sequentially.
121
186
122
-
In the library, this is the `Jsont_pointer.get` function:
187
+
Let's use the example JSON document from RFC 6901, Section 5:
123
188
124
-
<!-- $MDX skip -->
125
189
```ocaml
126
-
val get : t -> Jsont.json -> Jsont.json
127
-
```
128
-
129
-
Let's use the example JSON document from RFC 6901, Section 5:
130
-
131
-
```sh
132
-
$ cat rfc6901_example.json
133
-
{
134
-
"foo": ["bar", "baz"],
135
-
"": 0,
136
-
"a/b": 1,
137
-
"c%d": 2,
138
-
"e^f": 3,
139
-
"g|h": 4,
140
-
"i\\j": 5,
141
-
"k\"l": 6,
142
-
" ": 7,
143
-
"m~n": 8
144
-
}
190
+
# let rfc_example = parse_json {|{
191
+
"foo": ["bar", "baz"],
192
+
"": 0,
193
+
"a/b": 1,
194
+
"c%d": 2,
195
+
"e^f": 3,
196
+
"g|h": 4,
197
+
"i\\j": 5,
198
+
"k\"l": 6,
199
+
" ": 7,
200
+
"m~n": 8
201
+
}|};;
202
+
val rfc_example : Jsont.json =
203
+
Jsont.Object
204
+
([(("foo", <abstr>),
205
+
Jsont.Array
206
+
([Jsont.String ("bar", <abstr>); Jsont.String ("baz", <abstr>)],
207
+
<abstr>));
208
+
(("", <abstr>), Jsont.Number (0., <abstr>));
209
+
(("a/b", <abstr>), Jsont.Number (1., <abstr>));
210
+
(("c%d", <abstr>), Jsont.Number (2., <abstr>));
211
+
(("e^f", <abstr>), Jsont.Number (3., <abstr>));
212
+
(("g|h", <abstr>), Jsont.Number (4., <abstr>));
213
+
(("i\\j", <abstr>), Jsont.Number (5., <abstr>));
214
+
(("k\"l", <abstr>), Jsont.Number (6., <abstr>));
215
+
((" ", <abstr>), Jsont.Number (7., <abstr>));
216
+
(("m~n", <abstr>), Jsont.Number (8., <abstr>))],
217
+
<abstr>)
145
218
```
146
219
147
220
This document is carefully constructed to exercise various edge cases!
148
221
149
222
### The Root Pointer
150
223
151
-
```sh
152
-
$ jsonpp eval rfc6901_example.json ""
153
-
OK: {"foo":["bar","baz"],"":0,"a/b":1,"c%d":2,"e^f":3,"g|h":4,"i\\j":5,"k\"l":6," ":7,"m~n":8}
154
-
```
155
-
156
-
The empty pointer returns the whole document. In OCaml, this is
157
-
`Jsont_pointer.root`:
158
-
159
-
<!-- $MDX skip -->
160
224
```ocaml
161
-
val root : t
162
-
(** The empty pointer that references the whole document. *)
225
+
# get root rfc_example |> json_to_string;;
226
+
- : string =
227
+
"{\"foo\":[\"bar\",\"baz\"],\"\":0,\"a/b\":1,\"c%d\":2,\"e^f\":3,\"g|h\":4,\"i\\\\j\":5,\"k\\\"l\":6,\" \":7,\"m~n\":8}"
163
228
```
164
229
230
+
The empty pointer (`root`) returns the whole document.
231
+
165
232
### Object Member Access
166
233
167
-
```sh
168
-
$ jsonpp eval rfc6901_example.json "/foo"
169
-
OK: ["bar","baz"]
234
+
```ocaml
235
+
# get (of_string "/foo") rfc_example |> json_to_string;;
236
+
- : string = "[\"bar\",\"baz\"]"
170
237
```
171
238
172
239
`/foo` accesses the member named `foo`, which is an array.
173
240
174
241
### Array Index Access
175
242
176
-
```sh
177
-
$ jsonpp eval rfc6901_example.json "/foo/0"
178
-
OK: "bar"
243
+
```ocaml
244
+
# get (of_string "/foo/0") rfc_example |> json_to_string;;
245
+
- : string = "\"bar\""
246
+
# get (of_string "/foo/1") rfc_example |> json_to_string;;
247
+
- : string = "\"baz\""
179
248
```
180
249
181
250
`/foo/0` first goes to `foo`, then accesses index 0 of the array.
182
251
183
-
```sh
184
-
$ jsonpp eval rfc6901_example.json "/foo/1"
185
-
OK: "baz"
186
-
```
187
-
188
-
Index 1 gives us the second element.
189
-
190
252
### Empty String as Key
191
253
192
254
JSON allows empty strings as object keys:
193
255
194
-
```sh
195
-
$ jsonpp eval rfc6901_example.json "/"
196
-
OK: 0
256
+
```ocaml
257
+
# get (of_string "/") rfc_example |> json_to_string;;
258
+
- : string = "0"
197
259
```
198
260
199
261
The pointer `/` has one token: the empty string. This accesses the member
···
203
265
204
266
The RFC example includes keys with `/` and `~` characters:
205
267
206
-
```sh
207
-
$ jsonpp eval rfc6901_example.json "/a~1b"
208
-
OK: 1
268
+
```ocaml
269
+
# get (of_string "/a~1b") rfc_example |> json_to_string;;
270
+
- : string = "1"
209
271
```
210
272
211
273
The token `a~1b` refers to the key `a/b`. We'll explain this escaping
212
274
[below](#escaping-special-characters).
213
275
214
-
```sh
215
-
$ jsonpp eval rfc6901_example.json "/m~0n"
216
-
OK: 8
276
+
```ocaml
277
+
# get (of_string "/m~0n") rfc_example |> json_to_string;;
278
+
- : string = "8"
217
279
```
218
280
219
281
The token `m~0n` refers to the key `m~n`.
220
282
221
283
**Important**: When using the OCaml library programmatically, you don't need
222
-
to worry about escaping. The `Index.Mem` variant holds the literal key name:
284
+
to worry about escaping. The `Mem` variant holds the literal key name:
223
285
224
-
<!-- $MDX skip -->
225
286
```ocaml
226
-
(* To access the key "a/b", just use the literal string *)
227
-
let pointer = Jsont_pointer.make [Mem "a/b"]
287
+
# let slash_ptr = make [`Mem "a/b"];;
288
+
val slash_ptr : t = <abstr>
289
+
# to_string slash_ptr;;
290
+
- : string = "/a~1b"
291
+
# get slash_ptr rfc_example |> json_to_string;;
292
+
- : string = "1"
293
+
```
228
294
229
-
(* The library escapes it when converting to string *)
230
-
let s = Jsont_pointer.to_string pointer (* "/a~1b" *)
231
-
```
295
+
The library escapes it when converting to string.
232
296
233
297
### Other Special Characters (No Escaping Needed)
234
298
235
299
Most characters don't need escaping in JSON Pointer strings:
236
300
237
-
```sh
238
-
$ jsonpp eval rfc6901_example.json "/c%d"
239
-
OK: 2
240
-
```
241
-
242
-
```sh
243
-
$ jsonpp eval rfc6901_example.json "/e^f"
244
-
OK: 3
245
-
```
246
-
247
-
```sh
248
-
$ jsonpp eval rfc6901_example.json "/g|h"
249
-
OK: 4
250
-
```
251
-
252
-
```sh
253
-
$ jsonpp eval rfc6901_example.json "/ "
254
-
OK: 7
301
+
```ocaml
302
+
# get (of_string "/c%d") rfc_example |> json_to_string;;
303
+
- : string = "2"
304
+
# get (of_string "/e^f") rfc_example |> json_to_string;;
305
+
- : string = "3"
306
+
# get (of_string "/g|h") rfc_example |> json_to_string;;
307
+
- : string = "4"
308
+
# get (of_string "/ ") rfc_example |> json_to_string;;
309
+
- : string = "7"
255
310
```
256
311
257
312
Even a space is a valid key character!
···
260
315
261
316
What happens when we try to access something that doesn't exist?
262
317
263
-
```sh
264
-
$ jsonpp eval rfc6901_example.json "/nonexistent"
265
-
ERROR: JSON Pointer: member 'nonexistent' not found
266
-
File "-":
318
+
```ocaml
319
+
# get_result (of_string "/nonexistent") rfc_example;;
320
+
- : (Jsont.json, Jsont.Error.t) result = Error ([], <abstr>, <abstr>)
321
+
# find (of_string "/nonexistent") rfc_example;;
322
+
- : Jsont.json option = None
267
323
```
268
324
269
325
Or an out-of-bounds array index:
270
326
271
-
```sh
272
-
$ jsonpp eval rfc6901_example.json "/foo/99"
273
-
ERROR: JSON Pointer: index 99 out of bounds (array has 2 elements)
274
-
File "-":
327
+
```ocaml
328
+
# find (of_string "/foo/99") rfc_example;;
329
+
- : Jsont.json option = None
275
330
```
276
331
277
332
Or try to index into a non-container:
278
333
279
-
```sh
280
-
$ jsonpp eval rfc6901_example.json "/foo/0/invalid"
281
-
ERROR: JSON Pointer: cannot index into string with 'invalid'
282
-
File "-":
334
+
```ocaml
335
+
# find (of_string "/foo/0/invalid") rfc_example;;
336
+
- : Jsont.json option = None
283
337
```
284
338
285
339
The library provides both exception-raising and result-returning variants:
···
303
357
304
358
> note that leading zeros are not allowed
305
359
306
-
```sh
307
-
$ jsonpp parse "/foo/0"
308
-
OK: [Mem:foo, Nth:0]
360
+
```ocaml
361
+
# show_pointer (of_string "/foo/0");;
362
+
- : string = "[Mem:foo, Nth:0]"
309
363
```
310
364
311
365
Zero itself is fine.
312
366
313
-
```sh
314
-
$ jsonpp parse "/foo/01"
315
-
OK: [Mem:foo, Mem:01]
367
+
```ocaml
368
+
# show_pointer (of_string "/foo/01");;
369
+
- : string = "[Mem:foo, Mem:01]"
316
370
```
317
371
318
372
But `01` has a leading zero, so it's NOT treated as an array index - it
···
329
383
This is primarily useful for JSON Patch operations (RFC 6902). Let's see
330
384
how it parses:
331
385
332
-
```sh
333
-
$ jsonpp parse "/foo/-"
334
-
OK: [Mem:foo, End]
386
+
```ocaml
387
+
# show_pointer (of_string "/foo/-");;
388
+
- : string = "[Mem:foo, End]"
335
389
```
336
390
337
391
The `-` is recognized as a special `End` index.
···
339
393
However, you cannot evaluate a pointer containing `-` because it refers
340
394
to a position that doesn't exist:
341
395
342
-
```sh
343
-
$ jsonpp eval rfc6901_example.json "/foo/-"
344
-
ERROR: JSON Pointer: '-' (end marker) refers to nonexistent array element
345
-
File "-":
396
+
```ocaml
397
+
# find (of_string "/foo/-") rfc_example;;
398
+
- : Jsont.json option = None
346
399
```
347
400
348
401
The RFC explains this:
···
363
416
364
417
The `add` operation inserts a value at a location:
365
418
366
-
```sh
367
-
$ jsonpp add '{"foo":"bar"}' '/baz' '"qux"'
368
-
{"foo":"bar","baz":"qux"}
369
-
```
370
-
371
-
In OCaml:
372
-
373
-
<!-- $MDX skip -->
374
419
```ocaml
375
-
val add : t -> Jsont.json -> value:Jsont.json -> Jsont.json
420
+
# let obj = parse_json {|{"foo":"bar"}|};;
421
+
val obj : Jsont.json =
422
+
Jsont.Object ([(("foo", <abstr>), Jsont.String ("bar", <abstr>))], <abstr>)
423
+
# add (of_string "/baz") obj ~value:(Jsont.Json.string "qux")
424
+
|> json_to_string;;
425
+
- : string = "{\"foo\":\"bar\",\"baz\":\"qux\"}"
376
426
```
377
427
378
428
For arrays, `add` inserts BEFORE the specified index:
379
429
380
-
```sh
381
-
$ jsonpp add '{"foo":["a","b"]}' '/foo/1' '"X"'
382
-
{"foo":["a","X","b"]}
430
+
```ocaml
431
+
# let arr_obj = parse_json {|{"foo":["a","b"]}|};;
432
+
val arr_obj : Jsont.json =
433
+
Jsont.Object
434
+
([(("foo", <abstr>),
435
+
Jsont.Array
436
+
([Jsont.String ("a", <abstr>); Jsont.String ("b", <abstr>)], <abstr>))],
437
+
<abstr>)
438
+
# add (of_string "/foo/1") arr_obj ~value:(Jsont.Json.string "X")
439
+
|> json_to_string;;
440
+
- : string = "{\"foo\":[\"a\",\"X\",\"b\"]}"
383
441
```
384
442
385
443
This is where the `-` marker shines - it appends to the end:
386
444
387
-
```sh
388
-
$ jsonpp add '{"foo":["a","b"]}' '/foo/-' '"c"'
389
-
{"foo":["a","b","c"]}
445
+
```ocaml
446
+
# add (of_string "/foo/-") arr_obj ~value:(Jsont.Json.string "c")
447
+
|> json_to_string;;
448
+
- : string = "{\"foo\":[\"a\",\"b\",\"c\"]}"
390
449
```
391
450
392
451
### Remove
393
452
394
453
The `remove` operation deletes a value:
395
454
396
-
```sh
397
-
$ jsonpp remove '{"foo":"bar","baz":"qux"}' '/baz'
398
-
{"foo":"bar"}
455
+
```ocaml
456
+
# let two_fields = parse_json {|{"foo":"bar","baz":"qux"}|};;
457
+
val two_fields : Jsont.json =
458
+
Jsont.Object
459
+
([(("foo", <abstr>), Jsont.String ("bar", <abstr>));
460
+
(("baz", <abstr>), Jsont.String ("qux", <abstr>))],
461
+
<abstr>)
462
+
# remove (of_string "/baz") two_fields |> json_to_string;;
463
+
- : string = "{\"foo\":\"bar\"}"
399
464
```
400
465
401
466
For arrays, it removes and shifts:
402
467
403
-
```sh
404
-
$ jsonpp remove '{"foo":["a","b","c"]}' '/foo/1'
405
-
{"foo":["a","c"]}
468
+
```ocaml
469
+
# let three_elem = parse_json {|{"foo":["a","b","c"]}|};;
470
+
val three_elem : Jsont.json =
471
+
Jsont.Object
472
+
([(("foo", <abstr>),
473
+
Jsont.Array
474
+
([Jsont.String ("a", <abstr>); Jsont.String ("b", <abstr>);
475
+
Jsont.String ("c", <abstr>)],
476
+
<abstr>))],
477
+
<abstr>)
478
+
# remove (of_string "/foo/1") three_elem |> json_to_string;;
479
+
- : string = "{\"foo\":[\"a\",\"c\"]}"
406
480
```
407
481
408
482
### Replace
409
483
410
484
The `replace` operation updates an existing value:
411
485
412
-
```sh
413
-
$ jsonpp replace '{"foo":"bar"}' '/foo' '"baz"'
414
-
{"foo":"baz"}
486
+
```ocaml
487
+
# replace (of_string "/foo") obj ~value:(Jsont.Json.string "baz")
488
+
|> json_to_string;;
489
+
- : string = "{\"foo\":\"baz\"}"
415
490
```
416
491
417
-
Unlike `add`, `replace` requires the target to already exist:
418
-
419
-
```sh
420
-
$ jsonpp replace '{"foo":"bar"}' '/nonexistent' '"value"'
421
-
ERROR: JSON Pointer: member 'nonexistent' not found
422
-
File "-":
423
-
```
492
+
Unlike `add`, `replace` requires the target to already exist.
493
+
Attempting to replace a nonexistent path raises an error.
424
494
425
495
### Move
426
496
427
497
The `move` operation relocates a value:
428
498
429
-
```sh
430
-
$ jsonpp move '{"foo":{"bar":"baz"},"qux":{}}' '/foo/bar' '/qux/thud'
431
-
{"foo":{},"qux":{"thud":"baz"}}
499
+
```ocaml
500
+
# let nested = parse_json {|{"foo":{"bar":"baz"},"qux":{}}|};;
501
+
val nested : Jsont.json =
502
+
Jsont.Object
503
+
([(("foo", <abstr>),
504
+
Jsont.Object
505
+
([(("bar", <abstr>), Jsont.String ("baz", <abstr>))], <abstr>));
506
+
(("qux", <abstr>), Jsont.Object ([], <abstr>))],
507
+
<abstr>)
508
+
# move ~from:(of_string "/foo/bar") ~path:(of_string "/qux/thud") nested
509
+
|> json_to_string;;
510
+
- : string = "{\"foo\":{},\"qux\":{\"thud\":\"baz\"}}"
432
511
```
433
512
434
513
### Copy
435
514
436
515
The `copy` operation duplicates a value:
437
516
438
-
```sh
439
-
$ jsonpp copy '{"foo":{"bar":"baz"}}' '/foo/bar' '/foo/qux'
440
-
{"foo":{"bar":"baz","qux":"baz"}}
517
+
```ocaml
518
+
# let to_copy = parse_json {|{"foo":{"bar":"baz"}}|};;
519
+
val to_copy : Jsont.json =
520
+
Jsont.Object
521
+
([(("foo", <abstr>),
522
+
Jsont.Object
523
+
([(("bar", <abstr>), Jsont.String ("baz", <abstr>))], <abstr>))],
524
+
<abstr>)
525
+
# copy ~from:(of_string "/foo/bar") ~path:(of_string "/foo/qux") to_copy
526
+
|> json_to_string;;
527
+
- : string = "{\"foo\":{\"bar\":\"baz\",\"qux\":\"baz\"}}"
441
528
```
442
529
443
530
### Test
444
531
445
532
The `test` operation verifies a value (useful in JSON Patch):
446
533
447
-
```sh
448
-
$ jsonpp test '{"foo":"bar"}' '/foo' '"bar"'
449
-
true
450
-
```
451
-
452
-
```sh
453
-
$ jsonpp test '{"foo":"bar"}' '/foo' '"baz"'
454
-
false
534
+
```ocaml
535
+
# test (of_string "/foo") obj ~expected:(Jsont.Json.string "bar");;
536
+
- : bool = true
537
+
# test (of_string "/foo") obj ~expected:(Jsont.Json.string "wrong");;
538
+
- : bool = false
455
539
```
456
540
457
541
## Escaping Special Characters
···
473
557
### The Library Handles Escaping Automatically
474
558
475
559
**Important**: When using `jsont-pointer` programmatically, you rarely need
476
-
to think about escaping. The `Index.Mem` variant stores unescaped strings,
560
+
to think about escaping. The `Mem` variant stores unescaped strings,
477
561
and escaping happens automatically during serialization:
478
562
479
-
<!-- $MDX skip -->
480
563
```ocaml
481
-
(* Create a pointer to key "a/b" - no escaping needed *)
482
-
let p = Jsont_pointer.make [Mem "a/b"]
483
-
484
-
(* Serialize to string - escaping happens automatically *)
485
-
let s = Jsont_pointer.to_string p (* Returns "/a~1b" *)
486
-
487
-
(* Parse from string - unescaping happens automatically *)
488
-
let p' = Jsont_pointer.of_string "/a~1b"
489
-
(* p' contains [Mem "a/b"] - the unescaped key *)
490
-
```
491
-
492
-
The `Token` module exposes the escaping functions if you need them:
493
-
494
-
<!-- $MDX skip -->
495
-
```ocaml
496
-
module Token : sig
497
-
val escape : string -> string (* "a/b" -> "a~1b" *)
498
-
val unescape : string -> string (* "a~1b" -> "a/b" *)
499
-
end
564
+
# let p = make [`Mem "a/b"];;
565
+
val p : t = <abstr>
566
+
# to_string p;;
567
+
- : string = "/a~1b"
568
+
# let p' = of_string "/a~1b";;
569
+
val p' : t = <abstr>
570
+
# show_pointer p';;
571
+
- : string = "[Mem:a/b]"
500
572
```
501
573
502
574
### Escaping in Action
503
575
504
-
Let's see escaping with the CLI tool:
505
-
506
-
```sh
507
-
$ jsonpp escape "hello"
508
-
hello
509
-
```
510
-
511
-
No special characters, no escaping needed.
512
-
513
-
```sh
514
-
$ jsonpp escape "a/b"
515
-
a~1b
516
-
```
576
+
The `Token` module exposes the escaping functions:
517
577
518
-
The `/` becomes `~1`.
519
-
520
-
```sh
521
-
$ jsonpp escape "a~b"
522
-
a~0b
578
+
```ocaml
579
+
# Token.escape "hello";;
580
+
- : string = "hello"
581
+
# Token.escape "a/b";;
582
+
- : string = "a~1b"
583
+
# Token.escape "a~b";;
584
+
- : string = "a~0b"
585
+
# Token.escape "~/";;
586
+
- : string = "~0~1"
523
587
```
524
588
525
-
The `~` becomes `~0`.
526
-
527
-
```sh
528
-
$ jsonpp escape "~/"
529
-
~0~1
530
-
```
531
-
532
-
Both characters are escaped.
533
-
534
589
### Unescaping
535
590
536
591
And the reverse process:
537
592
538
-
```sh
539
-
$ jsonpp unescape "a~1b"
540
-
OK: a/b
541
-
```
542
-
543
-
```sh
544
-
$ jsonpp unescape "a~0b"
545
-
OK: a~b
593
+
```ocaml
594
+
# Token.unescape "a~1b";;
595
+
- : string = "a/b"
596
+
# Token.unescape "a~0b";;
597
+
- : string = "a~b"
546
598
```
547
599
548
600
### The Order Matters!
···
559
611
560
612
Let's verify this tricky case:
561
613
562
-
```sh
563
-
$ jsonpp unescape "~01"
564
-
OK: ~1
614
+
```ocaml
615
+
# Token.unescape "~01";;
616
+
- : string = "~1"
565
617
```
566
618
567
619
If we unescaped `~0` first, `~01` would become `~1`, which would then become
568
620
`/`. But that's wrong! The sequence `~01` should become the literal string
569
621
`~1` (a tilde followed by the digit one).
570
622
571
-
Invalid escape sequences are rejected:
572
-
573
-
```sh
574
-
$ jsonpp unescape "~2"
575
-
ERROR: Invalid JSON Pointer: invalid escape sequence ~2
576
-
```
577
-
578
-
```sh
579
-
$ jsonpp unescape "hello~"
580
-
ERROR: Invalid JSON Pointer: incomplete escape sequence at end
581
-
```
582
-
583
623
## URI Fragment Encoding
584
624
585
625
JSON Pointers can be embedded in URIs. RFC 6901, Section 6 explains:
···
590
630
591
631
This adds percent-encoding on top of the `~0`/`~1` escaping:
592
632
593
-
```sh
594
-
$ jsonpp uri-fragment "/foo"
595
-
OK: /foo -> /foo
633
+
```ocaml
634
+
# to_uri_fragment (of_string "/foo");;
635
+
- : string = "/foo"
636
+
# to_uri_fragment (of_string "/a~1b");;
637
+
- : string = "/a~1b"
638
+
# to_uri_fragment (of_string "/c%d");;
639
+
- : string = "/c%25d"
640
+
# to_uri_fragment (of_string "/ ");;
641
+
- : string = "/%20"
596
642
```
597
643
598
-
Simple pointers often don't need percent-encoding.
599
-
600
-
```sh
601
-
$ jsonpp uri-fragment "/a~1b"
602
-
OK: /a~1b -> /a~1b
603
-
```
604
-
605
-
The `~1` escape stays as-is (it's valid in URI fragments).
606
-
607
-
```sh
608
-
$ jsonpp uri-fragment "/c%d"
609
-
OK: /c%d -> /c%25d
610
-
```
611
-
612
-
The `%` character must be percent-encoded as `%25` in URIs!
613
-
614
-
```sh
615
-
$ jsonpp uri-fragment "/ "
616
-
OK: / -> /%20
617
-
```
618
-
619
-
Spaces become `%20`.
620
-
621
-
The library provides functions for URI fragment encoding:
622
-
623
-
<!-- $MDX skip -->
624
-
```ocaml
625
-
val to_uri_fragment : t -> string
626
-
val of_uri_fragment : string -> t
627
-
val jsont_uri_fragment : t Jsont.t
628
-
```
644
+
The `%` character must be percent-encoded as `%25` in URIs, and
645
+
spaces become `%20`.
629
646
630
647
Here's the RFC example showing the URI fragment forms:
631
648
···
640
657
| `"/ "` | `#/%20` | `7` |
641
658
| `"/m~0n"` | `#/m~0n` | `8` |
642
659
643
-
## Deeply Nested Structures
660
+
## Building Pointers Programmatically
644
661
645
-
JSON Pointer handles arbitrarily deep nesting:
662
+
Instead of parsing strings, you can build pointers from indices:
646
663
647
-
```sh
648
-
$ jsonpp eval rfc6901_example.json "/foo/0"
649
-
OK: "bar"
664
+
```ocaml
665
+
# let port_ptr = make [`Mem "database"; `Mem "port"];;
666
+
val port_ptr : t = <abstr>
667
+
# to_string port_ptr;;
668
+
- : string = "/database/port"
650
669
```
651
670
652
-
For deeper structures, just add more path segments. With nested objects:
671
+
For array access, use `Nth`:
653
672
654
-
```sh
655
-
$ jsonpp add '{"a":{"b":{"c":"d"}}}' '/a/b/x' '"y"'
656
-
{"a":{"b":{"c":"d","x":"y"}}}
673
+
```ocaml
674
+
# let first_feature_ptr = make [`Mem "features"; `Nth 0];;
675
+
val first_feature_ptr : t = <abstr>
676
+
# to_string first_feature_ptr;;
677
+
- : string = "/features/0"
657
678
```
658
679
659
-
With nested arrays:
680
+
### Pointer Navigation
660
681
661
-
```sh
662
-
$ jsonpp add '{"arr":[[1,2],[3,4]]}' '/arr/0/1' '99'
663
-
{"arr":[[1,99,2],[3,4]]}
682
+
You can build pointers incrementally using `append`:
683
+
684
+
```ocaml
685
+
# let db_ptr = of_string "/database";;
686
+
val db_ptr : t = <abstr>
687
+
# let creds_ptr = append db_ptr (`Mem "credentials");;
688
+
val creds_ptr : t = <abstr>
689
+
# let user_ptr = append creds_ptr (`Mem "username");;
690
+
val user_ptr : t = <abstr>
691
+
# to_string user_ptr;;
692
+
- : string = "/database/credentials/username"
693
+
```
694
+
695
+
Or concatenate two pointers:
696
+
697
+
```ocaml
698
+
# let base = of_string "/api/v1";;
699
+
val base : t = <abstr>
700
+
# let endpoint = of_string "/users/0";;
701
+
val endpoint : t = <abstr>
702
+
# to_string (concat base endpoint);;
703
+
- : string = "/api/v1/users/0"
664
704
```
665
705
666
706
## Jsont Integration
···
670
710
because you can point to a location in a JSON document and decode it
671
711
directly to an OCaml type.
672
712
673
-
Let's set up our OCaml environment and explore these features:
674
-
675
-
```ocaml
676
-
# open Jsont_pointer;;
677
-
# let parse_json s =
678
-
match Jsont_bytesrw.decode_string Jsont.json s with
679
-
| Ok json -> json
680
-
| Error e -> failwith e;;
681
-
val parse_json : string -> Jsont.json = <fun>
682
-
# let json_to_string json =
683
-
match Jsont_bytesrw.encode_string ~format:Jsont.Minify Jsont.json json with
684
-
| Ok s -> s
685
-
| Error e -> failwith e;;
686
-
val json_to_string : Jsont.json -> string = <fun>
687
-
```
688
-
689
-
### Working with JSON Values
690
-
691
-
Let's create a sample configuration document:
692
-
693
713
```ocaml
694
714
# let config_json = parse_json {|{
695
715
"database": {
···
719
739
<abstr>)
720
740
```
721
741
722
-
### Creating and Using Pointers
723
-
724
-
Create a pointer and use it to extract values:
725
-
726
-
```ocaml
727
-
# let host_ptr = of_string "/database/host";;
728
-
val host_ptr : t = <abstr>
729
-
# let host_value = get host_ptr config_json;;
730
-
val host_value : Jsont.json = Jsont.String ("localhost", <abstr>)
731
-
# match host_value with
732
-
| Jsont.String (s, _) -> s
733
-
| _ -> failwith "expected string";;
734
-
- : string = "localhost"
735
-
```
736
-
737
-
### Building Pointers Programmatically
738
-
739
-
Instead of parsing strings, you can build pointers from indices:
740
-
741
-
```ocaml
742
-
# let port_ptr = make [Mem "database"; Mem "port"];;
743
-
val port_ptr : t = <abstr>
744
-
# to_string port_ptr;;
745
-
- : string = "/database/port"
746
-
# match get port_ptr config_json with
747
-
| Jsont.Number (n, _) -> int_of_float n
748
-
| _ -> failwith "expected number";;
749
-
- : int = 5432
750
-
```
751
-
752
-
For array access, use `Nth`:
753
-
754
-
```ocaml
755
-
# let first_feature_ptr = make [Mem "features"; Nth 0];;
756
-
val first_feature_ptr : t = <abstr>
757
-
# match get first_feature_ptr config_json with
758
-
| Jsont.String (s, _) -> s
759
-
| _ -> failwith "expected string";;
760
-
- : string = "auth"
761
-
```
762
-
763
-
### Pointer Navigation
764
-
765
-
You can build pointers incrementally using `append`:
766
-
767
-
```ocaml
768
-
# let db_ptr = of_string "/database";;
769
-
val db_ptr : t = <abstr>
770
-
# let creds_ptr = append db_ptr (Mem "credentials");;
771
-
val creds_ptr : t = <abstr>
772
-
# let user_ptr = append creds_ptr (Mem "username");;
773
-
val user_ptr : t = <abstr>
774
-
# to_string user_ptr;;
775
-
- : string = "/database/credentials/username"
776
-
# match get user_ptr config_json with
777
-
| Jsont.String (s, _) -> s
778
-
| _ -> failwith "expected string";;
779
-
- : string = "admin"
780
-
```
781
-
782
-
### Safe Access with `find`
783
-
784
-
Use `find` when you're not sure if a path exists:
785
-
786
-
```ocaml
787
-
# find (of_string "/database/timeout") config_json;;
788
-
- : Jsont.json option = None
789
-
# find (of_string "/database/host") config_json |> Option.is_some;;
790
-
- : bool = true
791
-
```
792
-
793
742
### Typed Access with `path`
794
743
795
744
The `path` combinator combines pointer navigation with typed decoding:
···
831
780
config_json
832
781
|> Result.get_ok;;
833
782
val timeout : int = 30
834
-
```
835
-
836
-
### Mutation Operations
837
-
838
-
The library provides mutation functions for modifying JSON:
839
-
840
-
```ocaml
841
-
# let sample = parse_json {|{"name": "Alice", "scores": [85, 92, 78]}|};;
842
-
val sample : Jsont.json =
843
-
Jsont.Object
844
-
([(("name", <abstr>), Jsont.String ("Alice", <abstr>));
845
-
(("scores", <abstr>),
846
-
Jsont.Array
847
-
([Jsont.Number (85., <abstr>); Jsont.Number (92., <abstr>);
848
-
Jsont.Number (78., <abstr>)],
849
-
<abstr>))],
850
-
<abstr>)
851
-
```
852
-
853
-
**Add** a new field:
854
-
855
-
```ocaml
856
-
# let with_email = add (of_string "/email") sample
857
-
~value:(Jsont.Json.string "alice@example.com");;
858
-
val with_email : Jsont.json =
859
-
Jsont.Object
860
-
([(("name", <abstr>), Jsont.String ("Alice", <abstr>));
861
-
(("scores", <abstr>),
862
-
Jsont.Array
863
-
([Jsont.Number (85., <abstr>); Jsont.Number (92., <abstr>);
864
-
Jsont.Number (78., <abstr>)],
865
-
<abstr>));
866
-
(("email", <abstr>), Jsont.String ("alice@example.com", <abstr>))],
867
-
<abstr>)
868
-
# json_to_string with_email;;
869
-
- : string =
870
-
"{\"name\":\"Alice\",\"scores\":[85,92,78],\"email\":\"alice@example.com\"}"
871
-
```
872
-
873
-
**Add** to an array using `-` (append):
874
-
875
-
```ocaml
876
-
# let with_new_score = add (of_string "/scores/-") sample
877
-
~value:(Jsont.Json.number 95.);;
878
-
val with_new_score : Jsont.json =
879
-
Jsont.Object
880
-
([(("name", <abstr>), Jsont.String ("Alice", <abstr>));
881
-
(("scores", <abstr>),
882
-
Jsont.Array
883
-
([Jsont.Number (85., <abstr>); Jsont.Number (92., <abstr>);
884
-
Jsont.Number (78., <abstr>); Jsont.Number (95., <abstr>)],
885
-
<abstr>))],
886
-
<abstr>)
887
-
# json_to_string with_new_score;;
888
-
- : string = "{\"name\":\"Alice\",\"scores\":[85,92,78,95]}"
889
-
```
890
-
891
-
**Replace** an existing value:
892
-
893
-
```ocaml
894
-
# let renamed = replace (of_string "/name") sample
895
-
~value:(Jsont.Json.string "Bob");;
896
-
val renamed : Jsont.json =
897
-
Jsont.Object
898
-
([(("name", <abstr>), Jsont.String ("Bob", <abstr>));
899
-
(("scores", <abstr>),
900
-
Jsont.Array
901
-
([Jsont.Number (85., <abstr>); Jsont.Number (92., <abstr>);
902
-
Jsont.Number (78., <abstr>)],
903
-
<abstr>))],
904
-
<abstr>)
905
-
# json_to_string renamed;;
906
-
- : string = "{\"name\":\"Bob\",\"scores\":[85,92,78]}"
907
-
```
908
-
909
-
**Remove** a value:
910
-
911
-
```ocaml
912
-
# let without_first = remove (of_string "/scores/0") sample;;
913
-
val without_first : Jsont.json =
914
-
Jsont.Object
915
-
([(("name", <abstr>), Jsont.String ("Alice", <abstr>));
916
-
(("scores", <abstr>),
917
-
Jsont.Array
918
-
([Jsont.Number (92., <abstr>); Jsont.Number (78., <abstr>)], <abstr>))],
919
-
<abstr>)
920
-
# json_to_string without_first;;
921
-
- : string = "{\"name\":\"Alice\",\"scores\":[92,78]}"
922
783
```
923
784
924
785
### Nested Path Extraction
+26
-29
src/jsont_pointer.ml
+26
-29
src/jsont_pointer.ml
···
56
56
57
57
(* Index type - represents how a token is interpreted in context *)
58
58
module Index = struct
59
-
type t =
60
-
| Mem of string
61
-
| Nth of int
62
-
| End
59
+
type t = [ `Mem of string | `Nth of int | `End ]
63
60
64
61
let pp ppf = function
65
-
| Mem s -> Format.fprintf ppf "/%s" (Token.escape s)
66
-
| Nth n -> Format.fprintf ppf "/%d" n
67
-
| End -> Format.fprintf ppf "/-"
62
+
| `Mem s -> Format.fprintf ppf "/%s" (Token.escape s)
63
+
| `Nth n -> Format.fprintf ppf "/%d" n
64
+
| `End -> Format.fprintf ppf "/-"
68
65
69
66
let equal i1 i2 = match i1, i2 with
70
-
| Mem s1, Mem s2 -> String.equal s1 s2
71
-
| Nth n1, Nth n2 -> Int.equal n1 n2
72
-
| End, End -> true
67
+
| `Mem s1, `Mem s2 -> String.equal s1 s2
68
+
| `Nth n1, `Nth n2 -> Int.equal n1 n2
69
+
| `End, `End -> true
73
70
| _ -> false
74
71
75
72
let compare i1 i2 = match i1, i2 with
76
-
| Mem s1, Mem s2 -> String.compare s1 s2
77
-
| Mem _, _ -> -1
78
-
| _, Mem _ -> 1
79
-
| Nth n1, Nth n2 -> Int.compare n1 n2
80
-
| Nth _, End -> -1
81
-
| End, Nth _ -> 1
82
-
| End, End -> 0
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
83
80
84
81
let of_path_index (idx : Jsont.Path.index) : t =
85
82
match idx with
86
-
| Jsont.Path.Mem (s, _meta) -> Mem s
87
-
| Jsont.Path.Nth (n, _meta) -> Nth n
83
+
| Jsont.Path.Mem (s, _meta) -> `Mem s
84
+
| Jsont.Path.Nth (n, _meta) -> `Nth n
88
85
89
86
let to_path_index (idx : t) : Jsont.Path.index option =
90
87
match idx with
91
-
| Mem s -> Some (Jsont.Path.Mem (s, Jsont.Meta.none))
92
-
| Nth n -> Some (Jsont.Path.Nth (n, Jsont.Meta.none))
93
-
| End -> None
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
94
91
end
95
92
96
93
(* Internal representation: raw unescaped tokens.
···
112
109
(* Convert to Index for a given JSON value type *)
113
110
let to_index seg ~for_array =
114
111
match seg with
115
-
| End -> Index.End
112
+
| End -> `End
116
113
| Token s ->
117
114
if for_array then
118
115
match Token.is_valid_array_index s with
119
-
| Some n -> Index.Nth n
120
-
| None -> Index.Mem s (* Invalid index becomes member for error msg *)
116
+
| Some n -> `Nth n
117
+
| None -> `Mem s (* Invalid index becomes member for error msg *)
121
118
else
122
-
Index.Mem s
119
+
`Mem s
123
120
124
121
(* Convert from Index *)
125
122
let of_index = function
126
-
| Index.End -> End
127
-
| Index.Mem s -> Token s
128
-
| Index.Nth n -> Token (string_of_int n)
123
+
| `End -> End
124
+
| `Mem s -> Token s
125
+
| `Nth n -> Token (string_of_int n)
129
126
end
130
127
131
128
(* Pointer type - list of segments *)
+24
-23
src/jsont_pointer.mli
+24
-23
src/jsont_pointer.mli
···
65
65
a numeric index or the special end-of-array marker [-]. *)
66
66
module Index : sig
67
67
68
-
type t =
69
-
| Mem of string
70
-
(** [Mem name] indexes into an object member with the given [name].
68
+
type t = [
69
+
| `Mem of string
70
+
(** [`Mem name] indexes into an object member with the given [name].
71
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).
72
+
| `Nth of int
73
+
(** [`Nth n] indexes into an array at position [n] (zero-based).
74
74
Must be non-negative and without leading zeros in string form
75
75
(except for [0] itself). *)
76
-
| End
77
-
(** [End] represents the [-] token, indicating the position after
76
+
| `End
77
+
(** [`End] represents the [-] token, indicating the position after
78
78
the last element of an array. This is used for append operations
79
79
in {!Jsont_pointer.add} and similar mutation functions.
80
-
Evaluating a pointer containing [End] with {!Jsont_pointer.get}
80
+
Evaluating a pointer containing [`End] with {!Jsont_pointer.get}
81
81
will raise an error since it refers to a nonexistent element. *)
82
+
]
82
83
83
84
val pp : Format.formatter -> t -> unit
84
85
(** [pp] formats an index in JSON Pointer string notation. *)
···
96
97
97
98
val to_path_index : t -> Jsont.Path.index option
98
99
(** [to_path_index idx] converts to a {!Jsont.Path.index}.
99
-
Returns [None] for {!End} since it has no equivalent in
100
+
Returns [None] for [`End] since it has no equivalent in
100
101
{!Jsont.Path}. *)
101
102
end
102
103
···
144
145
The string must be either empty (representing the root) or start
145
146
with [/]. Each segment between [/] characters is unescaped as a
146
147
reference token. Segments that are valid non-negative integers
147
-
without leading zeros become {!Index.Nth} indices; the string [-]
148
-
becomes {!Index.End}; all others become {!Index.Mem}.
148
+
without leading zeros become [`Nth] indices; the string [-]
149
+
becomes [`End]; all others become [`Mem].
149
150
150
151
@raise Jsont.Error if [s] has invalid syntax:
151
152
- Non-empty string not starting with [/]
···
204
205
205
206
val to_path : t -> Jsont.Path.t option
206
207
(** [to_path p] converts to a {!Jsont.Path.t}.
207
-
Returns [None] if [p] contains an {!Index.End} index. *)
208
+
Returns [None] if [p] contains an [`End] index. *)
208
209
209
210
val to_path_exn : t -> Jsont.Path.t
210
211
(** [to_path_exn p] is like {!to_path} but raises {!Jsont.Error}
···
221
222
@raise Jsont.Error if:
222
223
- The pointer references a nonexistent object member
223
224
- The pointer references an out-of-bounds array index
224
-
- The pointer contains {!Index.End} (since [-] always refers
225
+
- The pointer contains [`End] (since [-] always refers
225
226
to a nonexistent element)
226
-
- An index type doesn't match the JSON value (e.g., {!Index.Nth}
227
+
- An index type doesn't match the JSON value (e.g., [`Nth]
227
228
on an object) *)
228
229
229
230
val get_result : t -> Jsont.json -> (Jsont.json, Jsont.Error.t) result
···
246
247
val set : t -> Jsont.json -> value:Jsont.json -> Jsont.json
247
248
(** [set p json ~value] replaces the value at pointer [p] with [value].
248
249
249
-
For {!Index.End} on arrays, appends [value] to the end of the array.
250
+
For [`End] on arrays, appends [value] to the end of the array.
250
251
251
252
@raise Jsont.Error if the pointer doesn't resolve to an existing
252
-
location (except for {!Index.End} on arrays). *)
253
+
location (except for [`End] on arrays). *)
253
254
254
255
val add : t -> Jsont.json -> value:Jsont.json -> Jsont.json
255
256
(** [add p json ~value] adds [value] at the location specified by [p].
···
258
259
{ul
259
260
{- For objects: If the member exists, it is replaced. If it doesn't
260
261
exist, a new member is added.}
261
-
{- For arrays with {!Index.Nth}: Inserts [value] {e before} the
262
+
{- For arrays with [`Nth]: Inserts [value] {e before} the
262
263
specified index, shifting subsequent elements. The index must be
263
264
valid (0 to length inclusive).}
264
-
{- For arrays with {!Index.End}: Appends [value] to the array.}}
265
+
{- For arrays with [`End]: Appends [value] to the array.}}
265
266
266
267
@raise Jsont.Error if:
267
268
- The parent of the target location doesn't exist
268
-
- An array index is out of bounds (except for {!Index.End})
269
+
- An array index is out of bounds (except for [`End])
269
270
- The parent is not an object or array *)
270
271
271
272
val remove : t -> Jsont.json -> Jsont.json
···
277
278
@raise Jsont.Error if:
278
279
- [p] is the root (cannot remove the root)
279
280
- The pointer doesn't resolve to an existing value
280
-
- The pointer contains {!Index.End} *)
281
+
- The pointer contains [`End] *)
281
282
282
283
val replace : t -> Jsont.json -> value:Jsont.json -> Jsont.json
283
284
(** [replace p json ~value] replaces the value at pointer [p] with [value].
···
286
287
287
288
@raise Jsont.Error if:
288
289
- The pointer doesn't resolve to an existing value
289
-
- The pointer contains {!Index.End} *)
290
+
- The pointer contains [`End] *)
290
291
291
292
val move : from:t -> path:t -> Jsont.json -> Jsont.json
292
293
(** [move ~from ~path json] moves the value from [from] to [path].
···
297
298
@raise Jsont.Error if:
298
299
- [from] doesn't resolve to a value
299
300
- [path] is a proper prefix of [from] (would create a cycle)
300
-
- Either pointer contains {!Index.End} *)
301
+
- Either pointer contains [`End] *)
301
302
302
303
val copy : from:t -> path:t -> Jsont.json -> Jsont.json
303
304
(** [copy ~from ~path json] copies the value from [from] to [path].
···
307
308
308
309
@raise Jsont.Error if:
309
310
- [from] doesn't resolve to a value
310
-
- Either pointer contains {!Index.End} *)
311
+
- Either pointer contains [`End] *)
311
312
312
313
val test : t -> Jsont.json -> expected:Jsont.json -> bool
313
314
(** [test p json ~expected] tests if the value at [p] equals [expected].
+3
-3
test/test_pointer.ml
+3
-3
test/test_pointer.ml
···
24
24
let indices = Jsont_pointer.indices p in
25
25
let index_strs = List.map (fun idx ->
26
26
match idx with
27
-
| Jsont_pointer.Index.Mem s -> Printf.sprintf "Mem:%s" s
28
-
| Jsont_pointer.Index.Nth n -> Printf.sprintf "Nth:%d" n
29
-
| Jsont_pointer.Index.End -> "End"
27
+
| `Mem s -> Printf.sprintf "Mem:%s" s
28
+
| `Nth n -> Printf.sprintf "Nth:%d" n
29
+
| `End -> "End"
30
30
) indices in
31
31
Printf.printf "OK: [%s]\n" (String.concat ", " index_strs)
32
32
with Jsont.Error e ->