···24 let indices = Jsont_pointer.indices p in
25 let index_strs = List.map (fun idx ->
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"
30 ) indices in
31 Printf.printf "OK: [%s]\n" (String.concat ", " index_strs)
32 with Jsont.Error e ->
···24 let indices = Jsont_pointer.indices p in
25 let index_strs = List.map (fun idx ->
26 match idx with
27+ | `Mem s -> Printf.sprintf "Mem:%s" s
28+ | `Nth n -> Printf.sprintf "Nth:%d" n
29+ | `End -> "End"
30 ) indices in
31 Printf.printf "OK: [%s]\n" (String.concat ", " index_strs)
32 with Jsont.Error e ->
···4[RFC 6901](https://www.rfc-editor.org/rfc/rfc6901), and demonstrates
5the `jsont-pointer` OCaml library through interactive examples.
60000000000000000000000000007## What is JSON Pointer?
89From RFC 6901, Section 1:
···1718For example, given this JSON document:
1920-```json
21-{
22- "users": [
23- {"name": "Alice", "age": 30},
24- {"name": "Bob", "age": 25}
25- ]
26-}
0000000000000027```
2829-The JSON Pointer `/users/0/name` refers to the string `"Alice"`.
00000003031In OCaml, this is represented by the `Jsont_pointer.t` type - a sequence
32of navigation steps from the document root to a target value.
···50- Every non-empty pointer starts with `/`
51- Everything between `/` characters is a "reference token"
5253-Let's see this in action. We can parse pointers and see their structure:
5455-```sh
56-$ jsonpp parse ""
57-OK: []
0058```
5960The empty pointer has no reference tokens - it points to the root.
6162-```sh
63-$ jsonpp parse "/foo"
64-OK: [Mem:foo]
0065```
6667The pointer `/foo` has one token: `foo`. Since it's not a number, it's
68interpreted as an object member name (`Mem`).
6970-```sh
71-$ jsonpp parse "/foo/0"
72-OK: [Mem:foo, Nth:0]
0073```
7475Here we have two tokens: `foo` (a member name) and `0` (interpreted as
76an array index `Nth`).
7778-```sh
79-$ jsonpp parse "/foo/bar/baz"
80-OK: [Mem:foo, Mem:bar, Mem:baz]
81```
8283Multiple tokens navigate deeper into nested structures.
···8889<!-- $MDX skip -->
90```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 *)
095```
9697The `Mem` variant holds the **unescaped** member name - you work with the
···102103What happens if a pointer doesn't start with `/`?
104105-```sh
106-$ jsonpp parse "foo"
107-ERROR: Invalid JSON Pointer: must be empty or start with '/': foo
108```
109110The RFC is strict: non-empty pointers MUST start with `/`.
1110000000000112## Evaluation: Navigating JSON
113114Now we come to the heart of JSON Pointer: evaluation. RFC 6901, Section 4
···119> the document. Each reference token in the JSON Pointer is evaluated
120> sequentially.
121122-In the library, this is the `Jsont_pointer.get` function:
123124-<!-- $MDX skip -->
125```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-}
000000000145```
146147This document is carefully constructed to exercise various edge cases!
148149### The Root Pointer
150151-```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```ocaml
161-val root : t
162-(** The empty pointer that references the whole document. *)
0163```
16400165### Object Member Access
166167-```sh
168-$ jsonpp eval rfc6901_example.json "/foo"
169-OK: ["bar","baz"]
170```
171172`/foo` accesses the member named `foo`, which is an array.
173174### Array Index Access
175176-```sh
177-$ jsonpp eval rfc6901_example.json "/foo/0"
178-OK: "bar"
00179```
180181`/foo/0` first goes to `foo`, then accesses index 0 of the array.
182183-```sh
184-$ jsonpp eval rfc6901_example.json "/foo/1"
185-OK: "baz"
186-```
187-188-Index 1 gives us the second element.
189-190### Empty String as Key
191192JSON allows empty strings as object keys:
193194-```sh
195-$ jsonpp eval rfc6901_example.json "/"
196-OK: 0
197```
198199The pointer `/` has one token: the empty string. This accesses the member
···203204The RFC example includes keys with `/` and `~` characters:
205206-```sh
207-$ jsonpp eval rfc6901_example.json "/a~1b"
208-OK: 1
209```
210211The token `a~1b` refers to the key `a/b`. We'll explain this escaping
212[below](#escaping-special-characters).
213214-```sh
215-$ jsonpp eval rfc6901_example.json "/m~0n"
216-OK: 8
217```
218219The token `m~0n` refers to the key `m~n`.
220221**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:
223224-<!-- $MDX skip -->
225```ocaml
226-(* To access the key "a/b", just use the literal string *)
227-let pointer = Jsont_pointer.make [Mem "a/b"]
00000228229-(* The library escapes it when converting to string *)
230-let s = Jsont_pointer.to_string pointer (* "/a~1b" *)
231-```
232233### Other Special Characters (No Escaping Needed)
234235Most characters don't need escaping in JSON Pointer strings:
236237-```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
255```
256257Even a space is a valid key character!
···260261What happens when we try to access something that doesn't exist?
262263-```sh
264-$ jsonpp eval rfc6901_example.json "/nonexistent"
265-ERROR: JSON Pointer: member 'nonexistent' not found
266-File "-":
0267```
268269Or an out-of-bounds array index:
270271-```sh
272-$ jsonpp eval rfc6901_example.json "/foo/99"
273-ERROR: JSON Pointer: index 99 out of bounds (array has 2 elements)
274-File "-":
275```
276277Or try to index into a non-container:
278279-```sh
280-$ jsonpp eval rfc6901_example.json "/foo/0/invalid"
281-ERROR: JSON Pointer: cannot index into string with 'invalid'
282-File "-":
283```
284285The library provides both exception-raising and result-returning variants:
···303304> note that leading zeros are not allowed
305306-```sh
307-$ jsonpp parse "/foo/0"
308-OK: [Mem:foo, Nth:0]
309```
310311Zero itself is fine.
312313-```sh
314-$ jsonpp parse "/foo/01"
315-OK: [Mem:foo, Mem:01]
316```
317318But `01` has a leading zero, so it's NOT treated as an array index - it
···329This is primarily useful for JSON Patch operations (RFC 6902). Let's see
330how it parses:
331332-```sh
333-$ jsonpp parse "/foo/-"
334-OK: [Mem:foo, End]
335```
336337The `-` is recognized as a special `End` index.
···339However, you cannot evaluate a pointer containing `-` because it refers
340to a position that doesn't exist:
341342-```sh
343-$ jsonpp eval rfc6901_example.json "/foo/-"
344-ERROR: JSON Pointer: '-' (end marker) refers to nonexistent array element
345-File "-":
346```
347348The RFC explains this:
···363364The `add` operation inserts a value at a location:
365366-```sh
367-$ jsonpp add '{"foo":"bar"}' '/baz' '"qux"'
368-{"foo":"bar","baz":"qux"}
369-```
370-371-In OCaml:
372-373-<!-- $MDX skip -->
374```ocaml
375-val add : t -> Jsont.json -> value:Jsont.json -> Jsont.json
00000376```
377378For arrays, `add` inserts BEFORE the specified index:
379380-```sh
381-$ jsonpp add '{"foo":["a","b"]}' '/foo/1' '"X"'
382-{"foo":["a","X","b"]}
00000000383```
384385This is where the `-` marker shines - it appends to the end:
386387-```sh
388-$ jsonpp add '{"foo":["a","b"]}' '/foo/-' '"c"'
389-{"foo":["a","b","c"]}
0390```
391392### Remove
393394The `remove` operation deletes a value:
395396-```sh
397-$ jsonpp remove '{"foo":"bar","baz":"qux"}' '/baz'
398-{"foo":"bar"}
000000399```
400401For arrays, it removes and shifts:
402403-```sh
404-$ jsonpp remove '{"foo":["a","b","c"]}' '/foo/1'
405-{"foo":["a","c"]}
000000000406```
407408### Replace
409410The `replace` operation updates an existing value:
411412-```sh
413-$ jsonpp replace '{"foo":"bar"}' '/foo' '"baz"'
414-{"foo":"baz"}
0415```
416417-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-```
424425### Move
426427The `move` operation relocates a value:
428429-```sh
430-$ jsonpp move '{"foo":{"bar":"baz"},"qux":{}}' '/foo/bar' '/qux/thud'
431-{"foo":{},"qux":{"thud":"baz"}}
000000000432```
433434### Copy
435436The `copy` operation duplicates a value:
437438-```sh
439-$ jsonpp copy '{"foo":{"bar":"baz"}}' '/foo/bar' '/foo/qux'
440-{"foo":{"bar":"baz","qux":"baz"}}
00000000441```
442443### Test
444445The `test` operation verifies a value (useful in JSON Patch):
446447-```sh
448-$ jsonpp test '{"foo":"bar"}' '/foo' '"bar"'
449-true
450-```
451-452-```sh
453-$ jsonpp test '{"foo":"bar"}' '/foo' '"baz"'
454-false
455```
456457## Escaping Special Characters
···473### The Library Handles Escaping Automatically
474475**Important**: When using `jsont-pointer` programmatically, you rarely need
476-to think about escaping. The `Index.Mem` variant stores unescaped strings,
477and escaping happens automatically during serialization:
478479-<!-- $MDX skip -->
480```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
500```
501502### Escaping in Action
503504-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-```
517518-The `/` becomes `~1`.
519-520-```sh
521-$ jsonpp escape "a~b"
522-a~0b
0000523```
524525-The `~` becomes `~0`.
526-527-```sh
528-$ jsonpp escape "~/"
529-~0~1
530-```
531-532-Both characters are escaped.
533-534### Unescaping
535536And the reverse process:
537538-```sh
539-$ jsonpp unescape "a~1b"
540-OK: a/b
541-```
542-543-```sh
544-$ jsonpp unescape "a~0b"
545-OK: a~b
546```
547548### The Order Matters!
···559560Let's verify this tricky case:
561562-```sh
563-$ jsonpp unescape "~01"
564-OK: ~1
565```
566567If we unescaped `~0` first, `~01` would become `~1`, which would then become
568`/`. But that's wrong! The sequence `~01` should become the literal string
569`~1` (a tilde followed by the digit one).
570571-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## URI Fragment Encoding
584585JSON Pointers can be embedded in URIs. RFC 6901, Section 6 explains:
···590591This adds percent-encoding on top of the `~0`/`~1` escaping:
592593-```sh
594-$ jsonpp uri-fragment "/foo"
595-OK: /foo -> /foo
000000596```
597598-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-```
629630Here's the RFC example showing the URI fragment forms:
631···640| `"/ "` | `#/%20` | `7` |
641| `"/m~0n"` | `#/m~0n` | `8` |
642643-## Deeply Nested Structures
644645-JSON Pointer handles arbitrarily deep nesting:
646647-```sh
648-$ jsonpp eval rfc6901_example.json "/foo/0"
649-OK: "bar"
00650```
651652-For deeper structures, just add more path segments. With nested objects:
653654-```sh
655-$ jsonpp add '{"a":{"b":{"c":"d"}}}' '/a/b/x' '"y"'
656-{"a":{"b":{"c":"d","x":"y"}}}
00657```
658659-With nested arrays:
660661-```sh
662-$ jsonpp add '{"arr":[[1,2],[3,4]]}' '/arr/0/1' '99'
663-{"arr":[[1,99,2],[3,4]]}
0000000000000000000664```
665666## Jsont Integration
···670because you can point to a location in a JSON document and decode it
671directly to an OCaml type.
672673-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```ocaml
694# let config_json = parse_json {|{
695 "database": {
···719 <abstr>)
720```
721722-### 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### Typed Access with `path`
794795The `path` combinator combines pointer navigation with typed decoding:
···831 config_json
832 |> Result.get_ok;;
833val 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```
923924### Nested Path Extraction
···4[RFC 6901](https://www.rfc-editor.org/rfc/rfc6901), and demonstrates
5the `jsont-pointer` OCaml library through interactive examples.
67+## 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+34## What is JSON Pointer?
3536From RFC 6901, Section 1:
···4445For example, given this JSON document:
4647+```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>)
68```
6970+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+```
7879In OCaml, this is represented by the `Jsont_pointer.t` type - a sequence
80of navigation steps from the document root to a target value.
···98- Every non-empty pointer starts with `/`
99- Everything between `/` characters is a "reference token"
100101+Let's see this in action:
102103+```ocaml
104+# let empty_ptr = of_string "";;
105+val empty_ptr : t = <abstr>
106+# show_pointer empty_ptr;;
107+- : string = "[]"
108```
109110The empty pointer has no reference tokens - it points to the root.
111112+```ocaml
113+# let foo_ptr = of_string "/foo";;
114+val foo_ptr : t = <abstr>
115+# show_pointer foo_ptr;;
116+- : string = "[Mem:foo]"
117```
118119The pointer `/foo` has one token: `foo`. Since it's not a number, it's
120interpreted as an object member name (`Mem`).
121122+```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]"
127```
128129Here we have two tokens: `foo` (a member name) and `0` (interpreted as
130an array index `Nth`).
131132+```ocaml
133+# show_pointer (of_string "/foo/bar/baz");;
134+- : string = "[Mem:foo, Mem:bar, Mem:baz]"
135```
136137Multiple tokens navigate deeper into nested structures.
···142143<!-- $MDX skip -->
144```ocaml
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+]
150```
151152The `Mem` variant holds the **unescaped** member name - you work with the
···157158What happens if a pointer doesn't start with `/`?
159160+```ocaml
161+# of_string "foo";;
162+Exception: Jsont.Error ([], <abstr>, <abstr>).
163```
164165The RFC is strict: non-empty pointers MUST start with `/`.
166167+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+177## Evaluation: Navigating JSON
178179Now we come to the heart of JSON Pointer: evaluation. RFC 6901, Section 4
···184> the document. Each reference token in the JSON Pointer is evaluated
185> sequentially.
186187+Let's use the example JSON document from RFC 6901, Section 5:
1880189```ocaml
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>)
218```
219220This document is carefully constructed to exercise various edge cases!
221222### The Root Pointer
223000000000224```ocaml
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}"
228```
229230+The empty pointer (`root`) returns the whole document.
231+232### Object Member Access
233234+```ocaml
235+# get (of_string "/foo") rfc_example |> json_to_string;;
236+- : string = "[\"bar\",\"baz\"]"
237```
238239`/foo` accesses the member named `foo`, which is an array.
240241### Array Index Access
242243+```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\""
248```
249250`/foo/0` first goes to `foo`, then accesses index 0 of the array.
2510000000252### Empty String as Key
253254JSON allows empty strings as object keys:
255256+```ocaml
257+# get (of_string "/") rfc_example |> json_to_string;;
258+- : string = "0"
259```
260261The pointer `/` has one token: the empty string. This accesses the member
···265266The RFC example includes keys with `/` and `~` characters:
267268+```ocaml
269+# get (of_string "/a~1b") rfc_example |> json_to_string;;
270+- : string = "1"
271```
272273The token `a~1b` refers to the key `a/b`. We'll explain this escaping
274[below](#escaping-special-characters).
275276+```ocaml
277+# get (of_string "/m~0n") rfc_example |> json_to_string;;
278+- : string = "8"
279```
280281The token `m~0n` refers to the key `m~n`.
282283**Important**: When using the OCaml library programmatically, you don't need
284+to worry about escaping. The `Mem` variant holds the literal key name:
2850286```ocaml
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+```
294295+The library escapes it when converting to string.
00296297### Other Special Characters (No Escaping Needed)
298299Most characters don't need escaping in JSON Pointer strings:
300301+```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"
000000000310```
311312Even a space is a valid key character!
···315316What happens when we try to access something that doesn't exist?
317318+```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
323```
324325Or an out-of-bounds array index:
326327+```ocaml
328+# find (of_string "/foo/99") rfc_example;;
329+- : Jsont.json option = None
0330```
331332Or try to index into a non-container:
333334+```ocaml
335+# find (of_string "/foo/0/invalid") rfc_example;;
336+- : Jsont.json option = None
0337```
338339The library provides both exception-raising and result-returning variants:
···357358> note that leading zeros are not allowed
359360+```ocaml
361+# show_pointer (of_string "/foo/0");;
362+- : string = "[Mem:foo, Nth:0]"
363```
364365Zero itself is fine.
366367+```ocaml
368+# show_pointer (of_string "/foo/01");;
369+- : string = "[Mem:foo, Mem:01]"
370```
371372But `01` has a leading zero, so it's NOT treated as an array index - it
···383This is primarily useful for JSON Patch operations (RFC 6902). Let's see
384how it parses:
385386+```ocaml
387+# show_pointer (of_string "/foo/-");;
388+- : string = "[Mem:foo, End]"
389```
390391The `-` is recognized as a special `End` index.
···393However, you cannot evaluate a pointer containing `-` because it refers
394to a position that doesn't exist:
395396+```ocaml
397+# find (of_string "/foo/-") rfc_example;;
398+- : Jsont.json option = None
0399```
400401The RFC explains this:
···416417The `add` operation inserts a value at a location:
41800000000419```ocaml
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\"}"
426```
427428For arrays, `add` inserts BEFORE the specified index:
429430+```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\"]}"
441```
442443This is where the `-` marker shines - it appends to the end:
444445+```ocaml
446+# add (of_string "/foo/-") arr_obj ~value:(Jsont.Json.string "c")
447+ |> json_to_string;;
448+- : string = "{\"foo\":[\"a\",\"b\",\"c\"]}"
449```
450451### Remove
452453The `remove` operation deletes a value:
454455+```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\"}"
464```
465466For arrays, it removes and shifts:
467468+```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\"]}"
480```
481482### Replace
483484The `replace` operation updates an existing value:
485486+```ocaml
487+# replace (of_string "/foo") obj ~value:(Jsont.Json.string "baz")
488+ |> json_to_string;;
489+- : string = "{\"foo\":\"baz\"}"
490```
491492+Unlike `add`, `replace` requires the target to already exist.
493+Attempting to replace a nonexistent path raises an error.
00000494495### Move
496497The `move` operation relocates a value:
498499+```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\"}}"
511```
512513### Copy
514515The `copy` operation duplicates a value:
516517+```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\"}}"
528```
529530### Test
531532The `test` operation verifies a value (useful in JSON Patch):
533534+```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
000539```
540541## Escaping Special Characters
···557### The Library Handles Escaping Automatically
558559**Important**: When using `jsont-pointer` programmatically, you rarely need
560+to think about escaping. The `Mem` variant stores unescaped strings,
561and escaping happens automatically during serialization:
5620563```ocaml
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]"
00000000000572```
573574### Escaping in Action
575576+The `Token` module exposes the escaping functions:
000000000000577578+```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"
587```
588000000000589### Unescaping
590591And the reverse process:
592593+```ocaml
594+# Token.unescape "a~1b";;
595+- : string = "a/b"
596+# Token.unescape "a~0b";;
597+- : string = "a~b"
000598```
599600### The Order Matters!
···611612Let's verify this tricky case:
613614+```ocaml
615+# Token.unescape "~01";;
616+- : string = "~1"
617```
618619If we unescaped `~0` first, `~01` would become `~1`, which would then become
620`/`. But that's wrong! The sequence `~01` should become the literal string
621`~1` (a tilde followed by the digit one).
622000000000000623## URI Fragment Encoding
624625JSON Pointers can be embedded in URIs. RFC 6901, Section 6 explains:
···630631This adds percent-encoding on top of the `~0`/`~1` escaping:
632633+```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"
642```
643644+The `%` character must be percent-encoded as `%25` in URIs, and
645+spaces become `%20`.
00000000000000000000000000000646647Here's the RFC example showing the URI fragment forms:
648···657| `"/ "` | `#/%20` | `7` |
658| `"/m~0n"` | `#/m~0n` | `8` |
659660+## Building Pointers Programmatically
661662+Instead of parsing strings, you can build pointers from indices:
663664+```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"
669```
670671+For array access, use `Nth`:
672673+```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"
678```
679680+### Pointer Navigation
681682+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"
704```
705706## Jsont Integration
···710because you can point to a location in a JSON document and decode it
711directly to an OCaml type.
71200000000000000000000713```ocaml
714# let config_json = parse_json {|{
715 "database": {
···739 <abstr>)
740```
74100000000000000000000000000000000000000000000000000000000000000000000000742### Typed Access with `path`
743744The `path` combinator combines pointer navigation with typed decoding:
···780 config_json
781 |> Result.get_ok;;
782val timeout : int = 30
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000783```
784785### Nested Path Extraction
+26-29
src/jsont_pointer.ml
···5657(* Index type - represents how a token is interpreted in context *)
58module Index = struct
59- type t =
60- | Mem of string
61- | Nth of int
62- | End
6364 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 "/-"
6869 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
73 | _ -> false
7475 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
8384 let of_path_index (idx : Jsont.Path.index) : t =
85 match idx with
86- | Jsont.Path.Mem (s, _meta) -> Mem s
87- | Jsont.Path.Nth (n, _meta) -> Nth n
8889 let to_path_index (idx : t) : Jsont.Path.index option =
90 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
94end
9596(* Internal representation: raw unescaped tokens.
···112 (* Convert to Index for a given JSON value type *)
113 let to_index seg ~for_array =
114 match seg with
115- | End -> Index.End
116 | Token s ->
117 if for_array then
118 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 *)
121 else
122- Index.Mem s
123124 (* Convert from Index *)
125 let of_index = function
126- | Index.End -> End
127- | Index.Mem s -> Token s
128- | Index.Nth n -> Token (string_of_int n)
129end
130131(* Pointer type - list of segments *)
···5657(* Index type - represents how a token is interpreted in context *)
58module Index = struct
59+ type t = [ `Mem of string | `Nth of int | `End ]
0006061 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 "/-"
6566 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
7172 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
8081 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
8586 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
91end
9293(* Internal representation: raw unescaped tokens.
···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
120121 (* Convert from Index *)
122 let of_index = function
123+ | `End -> End
124+ | `Mem s -> Token s
125+ | `Nth n -> Token (string_of_int n)
126end
127128(* Pointer type - list of segments *)
+24-23
src/jsont_pointer.mli
···65 a numeric index or the special end-of-array marker [-]. *)
66module Index : sig
6768- 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. *)
08283 val pp : Format.formatter -> t -> unit
84 (** [pp] formats an index in JSON Pointer string notation. *)
···9697 val to_path_index : t -> Jsont.Path.index option
98 (** [to_path_index idx] converts to a {!Jsont.Path.index}.
99- Returns [None] for {!End} since it has no equivalent in
100 {!Jsont.Path}. *)
101end
102···144 The string must be either empty (representing the root) or start
145 with [/]. Each segment between [/] characters is unescaped as a
146 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}.
149150 @raise Jsont.Error if [s] has invalid syntax:
151 - Non-empty string not starting with [/]
···204205val to_path : t -> Jsont.Path.t option
206(** [to_path p] converts to a {!Jsont.Path.t}.
207- Returns [None] if [p] contains an {!Index.End} index. *)
208209val to_path_exn : t -> Jsont.Path.t
210(** [to_path_exn p] is like {!to_path} but raises {!Jsont.Error}
···221 @raise Jsont.Error if:
222 - The pointer references a nonexistent object member
223 - The pointer references an out-of-bounds array index
224- - The pointer contains {!Index.End} (since [-] always refers
225 to a nonexistent element)
226- - An index type doesn't match the JSON value (e.g., {!Index.Nth}
227 on an object) *)
228229val get_result : t -> Jsont.json -> (Jsont.json, Jsont.Error.t) result
···246val set : t -> Jsont.json -> value:Jsont.json -> Jsont.json
247(** [set p json ~value] replaces the value at pointer [p] with [value].
248249- For {!Index.End} on arrays, appends [value] to the end of the array.
250251 @raise Jsont.Error if the pointer doesn't resolve to an existing
252- location (except for {!Index.End} on arrays). *)
253254val add : t -> Jsont.json -> value:Jsont.json -> Jsont.json
255(** [add p json ~value] adds [value] at the location specified by [p].
···258 {ul
259 {- For objects: If the member exists, it is replaced. If it doesn't
260 exist, a new member is added.}
261- {- For arrays with {!Index.Nth}: Inserts [value] {e before} the
262 specified index, shifting subsequent elements. The index must be
263 valid (0 to length inclusive).}
264- {- For arrays with {!Index.End}: Appends [value] to the array.}}
265266 @raise Jsont.Error if:
267 - The parent of the target location doesn't exist
268- - An array index is out of bounds (except for {!Index.End})
269 - The parent is not an object or array *)
270271val remove : t -> Jsont.json -> Jsont.json
···277 @raise Jsont.Error if:
278 - [p] is the root (cannot remove the root)
279 - The pointer doesn't resolve to an existing value
280- - The pointer contains {!Index.End} *)
281282val replace : t -> Jsont.json -> value:Jsont.json -> Jsont.json
283(** [replace p json ~value] replaces the value at pointer [p] with [value].
···286287 @raise Jsont.Error if:
288 - The pointer doesn't resolve to an existing value
289- - The pointer contains {!Index.End} *)
290291val move : from:t -> path:t -> Jsont.json -> Jsont.json
292(** [move ~from ~path json] moves the value from [from] to [path].
···297 @raise Jsont.Error if:
298 - [from] doesn't resolve to a value
299 - [path] is a proper prefix of [from] (would create a cycle)
300- - Either pointer contains {!Index.End} *)
301302val copy : from:t -> path:t -> Jsont.json -> Jsont.json
303(** [copy ~from ~path json] copies the value from [from] to [path].
···307308 @raise Jsont.Error if:
309 - [from] doesn't resolve to a value
310- - Either pointer contains {!Index.End} *)
311312val test : t -> Jsont.json -> expected:Jsont.json -> bool
313(** [test p json ~expected] tests if the value at [p] equals [expected].
···65 a numeric index or the special end-of-array marker [-]. *)
66module Index : sig
6768+ 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+ ]
8384 val pp : Format.formatter -> t -> unit
85 (** [pp] formats an index in JSON Pointer string notation. *)
···9798 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}. *)
102end
103···145 The string must be either empty (representing the root) or start
146 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].
150151 @raise Jsont.Error if [s] has invalid syntax:
152 - Non-empty string not starting with [/]
···205206val to_path : t -> Jsont.Path.t option
207(** [to_path p] converts to a {!Jsont.Path.t}.
208+ Returns [None] if [p] contains an [`End] index. *)
209210val to_path_exn : t -> Jsont.Path.t
211(** [to_path_exn p] is like {!to_path} but raises {!Jsont.Error}
···222 @raise Jsont.Error if:
223 - The pointer references a nonexistent object member
224 - The pointer references an out-of-bounds array index
225+ - The pointer contains [`End] (since [-] always refers
226 to a nonexistent element)
227+ - An index type doesn't match the JSON value (e.g., [`Nth]
228 on an object) *)
229230val get_result : t -> Jsont.json -> (Jsont.json, Jsont.Error.t) result
···247val set : t -> Jsont.json -> value:Jsont.json -> Jsont.json
248(** [set p json ~value] replaces the value at pointer [p] with [value].
249250+ For [`End] on arrays, appends [value] to the end of the array.
251252 @raise Jsont.Error if the pointer doesn't resolve to an existing
253+ location (except for [`End] on arrays). *)
254255val add : t -> Jsont.json -> value:Jsont.json -> Jsont.json
256(** [add p json ~value] adds [value] at the location specified by [p].
···259 {ul
260 {- For objects: If the member exists, it is replaced. If it doesn't
261 exist, a new member is added.}
262+ {- For arrays with [`Nth]: Inserts [value] {e before} the
263 specified index, shifting subsequent elements. The index must be
264 valid (0 to length inclusive).}
265+ {- For arrays with [`End]: Appends [value] to the array.}}
266267 @raise Jsont.Error if:
268 - The parent of the target location doesn't exist
269+ - An array index is out of bounds (except for [`End])
270 - The parent is not an object or array *)
271272val remove : t -> Jsont.json -> Jsont.json
···278 @raise Jsont.Error if:
279 - [p] is the root (cannot remove the root)
280 - The pointer doesn't resolve to an existing value
281+ - The pointer contains [`End] *)
282283val replace : t -> Jsont.json -> value:Jsont.json -> Jsont.json
284(** [replace p json ~value] replaces the value at pointer [p] with [value].
···287288 @raise Jsont.Error if:
289 - The pointer doesn't resolve to an existing value
290+ - The pointer contains [`End] *)
291292val move : from:t -> path:t -> Jsont.json -> Jsont.json
293(** [move ~from ~path json] moves the value from [from] to [path].
···298 @raise Jsont.Error if:
299 - [from] doesn't resolve to a value
300 - [path] is a proper prefix of [from] (would create a cycle)
301+ - Either pointer contains [`End] *)
302303val copy : from:t -> path:t -> Jsont.json -> Jsont.json
304(** [copy ~from ~path json] copies the value from [from] to [path].
···308309 @raise Jsont.Error if:
310 - [from] doesn't resolve to a value
311+ - Either pointer contains [`End] *)
312313val test : t -> Jsont.json -> expected:Jsont.json -> bool
314(** [test p json ~expected] tests if the value at [p] equals [expected].
+3-3
test/test_pointer.ml
···24 let indices = Jsont_pointer.indices p in
25 let index_strs = List.map (fun idx ->
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"
30 ) indices in
31 Printf.printf "OK: [%s]\n" (String.concat ", " index_strs)
32 with Jsont.Error e ->
···24 let indices = Jsont_pointer.indices p in
25 let index_strs = List.map (fun idx ->
26 match idx with
27+ | `Mem s -> Printf.sprintf "Mem:%s" s
28+ | `Nth n -> Printf.sprintf "Nth:%d" n
29+ | `End -> "End"
30 ) indices in
31 Printf.printf "OK: [%s]\n" (String.concat ", " index_strs)
32 with Jsont.Error e ->