···1717 | Ok s -> s
1818 | Error e -> failwith e
19192020-(* Helper to get indices from either nav or append pointer *)
2121-let indices_of_result (result : [ `Nav of Jsont_pointer.nav Jsont_pointer.t
2222- | `Append of Jsont_pointer.append Jsont_pointer.t ]) =
2323- match result with
2424- | `Nav p -> Jsont_pointer.indices p
2525- | `Append p -> Jsont_pointer.indices p
2020+(* Helper to get indices from any pointer *)
2121+let indices_of_any (Jsont_pointer.Any p) = Jsont_pointer.indices p
26222727-(* Helper to convert to string from either nav or append pointer *)
2828-let to_string_of_result (result : [ `Nav of Jsont_pointer.nav Jsont_pointer.t
2929- | `Append of Jsont_pointer.append Jsont_pointer.t ]) =
3030- match result with
3131- | `Nav p -> Jsont_pointer.to_string p
3232- | `Append p -> Jsont_pointer.to_string p
2323+(* Helper to convert to string from any pointer *)
2424+let to_string_of_any (Jsont_pointer.Any p) = Jsont_pointer.to_string p
2525+2626+(* Helper to check if pointer is append *)
2727+let is_append_any (Jsont_pointer.Any p : Jsont_pointer.any) =
2828+ not (Jsont_pointer.is_nav (Jsont_pointer.Any p))
33293430(* Test: parse pointer and print indices *)
3531let test_parse pointer_str =
3632 try
3733 let result = Jsont_pointer.of_string pointer_str in
3838- let indices = indices_of_result result in
3434+ let indices = indices_of_any result in
3935 let index_strs = List.map (fun idx ->
4036 match idx with
4137 | Jsont.Path.Mem (s, _) -> Printf.sprintf "Mem:%s" s
4238 | Jsont.Path.Nth (n, _) -> Printf.sprintf "Nth:%d" n
4339 ) indices in
4444- let suffix = match result with `Nav _ -> "" | `Append _ -> ", /-" in
4040+ let suffix = if is_append_any result then ", /-" else "" in
4541 Printf.printf "OK: [%s%s]\n" (String.concat ", " index_strs) suffix
4642 with Jsont.Error e ->
4743 Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e)
···5046let test_roundtrip pointer_str =
5147 try
5248 let result = Jsont_pointer.of_string pointer_str in
5353- let s = to_string_of_result result in
4949+ let s = to_string_of_any result in
5450 if s = pointer_str then
5551 Printf.printf "OK: %s\n" s
5652 else
···8884let test_uri_fragment pointer_str =
8985 try
9086 let result = Jsont_pointer.of_string pointer_str in
9191- let frag = match result with
9292- | `Nav p -> Jsont_pointer.to_uri_fragment p
9393- | `Append p -> Jsont_pointer.to_uri_fragment p
9494- in
8787+ let (Jsont_pointer.Any p) = result in
8888+ let frag = Jsont_pointer.to_uri_fragment p in
9589 let result2 = Jsont_pointer.of_uri_fragment frag in
9696- let s2 = to_string_of_result result2 in
9090+ let s2 = to_string_of_any result2 in
9791 if s2 = pointer_str then
9892 Printf.printf "OK: %s -> %s\n" pointer_str frag
9993 else
···106100 try
107101 let json = parse_json json_str in
108102 let value = parse_json value_str in
109109- let result = match Jsont_pointer.of_string pointer_str with
110110- | `Nav p -> Jsont_pointer.add p json ~value
111111- | `Append p -> Jsont_pointer.add p json ~value
112112- in
103103+ let p = Jsont_pointer.of_string pointer_str in
104104+ let result = Jsont_pointer.add p json ~value in
113105 Printf.printf "%s\n" (json_to_string result)
114106 with Jsont.Error e ->
115107 Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e)
···140132 try
141133 let json = parse_json json_str in
142134 let from = Jsont_pointer.of_string_nav from_str in
143143- let result = match Jsont_pointer.of_string path_str with
144144- | `Nav path -> Jsont_pointer.move ~from ~path json
145145- | `Append path -> Jsont_pointer.move ~from ~path json
146146- in
135135+ let path = Jsont_pointer.of_string path_str in
136136+ let result = Jsont_pointer.move ~from ~path json in
147137 Printf.printf "%s\n" (json_to_string result)
148138 with Jsont.Error e ->
149139 Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e)
···153143 try
154144 let json = parse_json json_str in
155145 let from = Jsont_pointer.of_string_nav from_str in
156156- let result = match Jsont_pointer.of_string path_str with
157157- | `Nav path -> Jsont_pointer.copy ~from ~path json
158158- | `Append path -> Jsont_pointer.copy ~from ~path json
159159- in
146146+ let path = Jsont_pointer.of_string path_str in
147147+ let result = Jsont_pointer.copy ~from ~path json in
160148 Printf.printf "%s\n" (json_to_string result)
161149 with Jsont.Error e ->
162150 Printf.printf "ERROR: %s\n" (Jsont.Error.to_string e)
+152-102
doc/tutorial.mld
···5454For example, given this JSON document:
55555656{x@ocaml[
5757-# let users_json =
5858- parse_json "{\"users\":[{\"name\":\"Alice\",\"age\":30},{\"name\":\"Bob\",\"age\":25}]}";;
5757+# let users_json = parse_json {|{
5858+ "users": [
5959+ {"name": "Alice", "age": 30},
6060+ {"name": "Bob", "age": 25}
6161+ ]
6262+ }|};;
5963val users_json : Jsont.json =
6064 {"users":[{"name":"Alice","age":30},{"name":"Bob","age":25}]}
6165]x}
···129133130134Each reference token is represented using {!Jsont.Path.index}:
131135132132-{[
136136+{v
133137type index = Jsont.Path.index
134138(* = Jsont.Path.Mem of string * Jsont.Meta.t
135139 | Jsont.Path.Nth of int * Jsont.Meta.t *)
136136-]}
140140+v}
137141138142The [Mem] constructor is for object member access, and [Nth] is for array
139143index access. The member name is {b unescaped} - you work with the actual
···156160157161{@ocaml[
158162# of_string_result "foo";;
159159-- : ([ `Append of append t | `Nav of nav t ], string) result =
163163+- : (any, string) result =
160164Error "Invalid JSON Pointer: must be empty or start with '/': foo"
161165# of_string_result "/valid";;
162162-- : ([ `Append of append t | `Nav of nav t ], string) result =
163163-Ok (`Nav [Mem "valid"])
166166+- : (any, string) result = Ok (Any <abstr>)
164167]}
165168166169{1 Evaluation: Navigating JSON}
···175178176179Let's use the example JSON document from RFC 6901, Section 5:
177180178178-{@ocaml[
179179-# let rfc_example = parse_json "{\"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}";;
181181+{x@ocaml[
182182+# let rfc_example = parse_json {|{
183183+ "foo": ["bar", "baz"],
184184+ "": 0,
185185+ "a/b": 1,
186186+ "c%d": 2,
187187+ "e^f": 3,
188188+ "g|h": 4,
189189+ "i\\j": 5,
190190+ "k\"l": 6,
191191+ " ": 7,
192192+ "m~n": 8
193193+ }|};;
180194val rfc_example : Jsont.json =
181195 {"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}
182182-]}
196196+]x}
183197184198This document is carefully constructed to exercise various edge cases!
185199···304318305319The library provides both exception-raising and result-returning variants:
306320307307-[
321321+{v
308322val get : nav t -> Jsont.json -> Jsont.json
309323val get_result : nav t -> Jsont.json -> (Jsont.json, Jsont.Error.t) result
310324val find : nav t -> Jsont.json -> Jsont.json option
311311-]
325325+v}
312326313327{2 Array Index Rules}
314328···355369between pointers that can be used for navigation and pointers that target
356370the "append position":
357371358358-{[
372372+{v
359373type nav (* A pointer to an existing element *)
360374type append (* A pointer ending with "-" (append position) *)
361375type 'a t (* Pointer with phantom type parameter *)
362362-]}
376376+type any (* Existential: wraps either nav or append *)
377377+v}
363378364364-When you parse a pointer, you get either a [nav t] or an [append t]:
379379+When you parse a pointer with {!of_string}, you get an {!any} pointer
380380+that can be used directly with mutation operations:
365381366382{@ocaml[
367383# of_string "/foo/0";;
368368-- : [ `Append of Jsont_pointer.append Jsont_pointer.t
369369- | `Nav of Jsont_pointer.nav Jsont_pointer.t ]
370370-= `Nav [Mem "foo"; Nth 0]
384384+- : any = Any <abstr>
371385# of_string "/foo/-";;
372372-- : [ `Append of Jsont_pointer.append Jsont_pointer.t
373373- | `Nav of Jsont_pointer.nav Jsont_pointer.t ]
374374-= `Append [Mem "foo"] /-
386386+- : any = Any <abstr>
375387]}
376388377377-The [-] creates an [append] pointer. Note that in the internal
378378-representation, the append position is tracked separately (shown as [/-]).
389389+The [-] creates an append pointer. The {!any} type wraps either kind,
390390+making it ergonomic to use with operations like {!set} and {!add}.
379391380380-{2 Why Phantom Types?}
392392+{2 Why Two Pointer Types?}
381393382394The RFC explains that [-] refers to a {e nonexistent} position:
383395···387399388400So you {b cannot use [get] or [find]} with an append pointer - it makes
389401no sense to retrieve a value from a position that doesn't exist! The
390390-library enforces this at compile time.
402402+library enforces this:
403403+- Use {!of_string_nav} when you need to call {!get} or {!find}
404404+- Use {!of_string} (returns {!any}) for mutation operations
391405392392-However, append pointers {b are} valid for mutation operations like {!add}:
406406+Mutation operations like {!add} accept {!any} directly:
393407394408{x@ocaml[
395395-# let arr_obj = parse_json "{\"foo\":[\"a\",\"b\"]}";;
409409+# let arr_obj = parse_json {|{"foo":["a","b"]}|};;
396410val arr_obj : Jsont.json = {"foo":["a","b"]}
397397-# (match of_string "/foo/-" with `Append p -> add p arr_obj ~value:(Jsont.Json.string "c") | `Nav _ -> assert false);;
411411+# add (of_string "/foo/-") arr_obj ~value:(Jsont.Json.string "c");;
398412- : Jsont.json = {"foo":["a","b","c"]}
399413]x}
400414401401-For convenience, use {!of_string_nav} when you know a pointer shouldn't
402402-contain [-]:
415415+For retrieval operations, use {!of_string_nav} which ensures the pointer
416416+doesn't contain [-]:
403417404418{@ocaml[
405419# of_string_nav "/foo/0";;
406406-- : Jsont_pointer.nav Jsont_pointer.t = [Mem "foo"; Nth 0]
420420+- : nav t = [Mem "foo"; Nth 0]
407421# of_string_nav "/foo/-";;
408422Exception:
409423Jsont.Error Invalid JSON Pointer: '-' not allowed in navigation pointer.
···415429416430{@ocaml[
417431# let nav_ptr = of_string_nav "/foo";;
418418-val nav_ptr : Jsont_pointer.nav Jsont_pointer.t = [Mem "foo"]
432432+val nav_ptr : nav t = [Mem "foo"]
419433# let app_ptr = at_end nav_ptr;;
420420-val app_ptr : Jsont_pointer.append Jsont_pointer.t = [Mem "foo"] /-
434434+val app_ptr : append t = [Mem "foo"] /-
421435# to_string app_ptr;;
422436- : string = "/foo/-"
423437]}
···428442(JSON Patch) uses JSON Pointer for modifications. The [jsont-pointer]
429443library provides these operations.
430444431431-{2 Which Pointer Type for Which Operation?}
432432-433433-The phantom type system enforces correct usage:
434434-435435-{ul
436436-{- {!get}, {!find} - [nav t] only - Can't retrieve from non-existent position}
437437-{- {!remove} - [nav t] only - Can't remove what doesn't exist}
438438-{- {!replace} - [nav t] only - Can't replace what doesn't exist}
439439-{- {!test} - [nav t] only - Can't test non-existent position}
440440-{- {!add} - [_ t] (both) - Can add at existing position OR append}
441441-{- {!set} - [_ t] (both) - Can set existing position OR append}
442442-{- {!move}, {!copy} - [from:nav t], [path:_ t] - Source must exist, dest can be append}
443443-}
444444-445445{2 Add}
446446447447-The {!add} operation inserts a value at a location:
447447+The {!add} operation inserts a value at a location. It accepts {!any}
448448+pointers, so you can use {!of_string} directly:
448449449449-{@ocaml[
450450-# let obj = parse_json "{\"foo\":\"bar\"}";;
450450+{x@ocaml[
451451+# let obj = parse_json {|{"foo":"bar"}|};;
451452val obj : Jsont.json = {"foo":"bar"}
452452-# add (of_string_nav "/baz") obj ~value:(Jsont.Json.string "qux")
453453- ;;
453453+# add (of_string "/baz") obj ~value:(Jsont.Json.string "qux");;
454454- : Jsont.json = {"foo":"bar","baz":"qux"}
455455-]}
455455+]x}
456456457457For arrays, {!add} inserts BEFORE the specified index:
458458459459{x@ocaml[
460460-# let arr_obj = parse_json "{\"foo\":[\"a\",\"b\"]}";;
460460+# let arr_obj = parse_json {|{"foo":["a","b"]}|};;
461461val arr_obj : Jsont.json = {"foo":["a","b"]}
462462-# add (of_string_nav "/foo/1") arr_obj ~value:(Jsont.Json.string "X")
463463- ;;
462462+# add (of_string "/foo/1") arr_obj ~value:(Jsont.Json.string "X");;
464463- : Jsont.json = {"foo":["a","X","b"]}
465464]x}
466465467467-This is where the [-] marker and append pointers shine - they append to the end:
466466+This is where the [-] marker shines - it appends to the end:
468467469468{x@ocaml[
470470-# (match of_string "/foo/-" with `Append p -> add p arr_obj ~value:(Jsont.Json.string "c") | `Nav _ -> assert false);;
469469+# add (of_string "/foo/-") arr_obj ~value:(Jsont.Json.string "c");;
471470- : Jsont.json = {"foo":["a","b","c"]}
472471]x}
473472474474-Or more conveniently using {!at_end}:
473473+You can also use {!at_end} to create an append pointer programmatically:
475474476475{x@ocaml[
477477-# add (at_end (of_string_nav "/foo")) arr_obj ~value:(Jsont.Json.string "c")
478478- ;;
476476+# add (any (at_end (of_string_nav "/foo"))) arr_obj ~value:(Jsont.Json.string "c");;
479477- : Jsont.json = {"foo":["a","b","c"]}
480478]x}
481479480480+{2 Ergonomic Mutation with [any]}
481481+482482+Since {!add}, {!set}, {!move}, and {!copy} accept {!any} pointers, you can
483483+use {!of_string} directly without any pattern matching. This makes JSON
484484+Patch implementations straightforward:
485485+486486+{x@ocaml[
487487+# let items = parse_json {|{"items":["x"]}|};;
488488+val items : Jsont.json = {"items":["x"]}
489489+# add (of_string "/items/0") items ~value:(Jsont.Json.string "y");;
490490+- : Jsont.json = {"items":["y","x"]}
491491+# add (of_string "/items/-") items ~value:(Jsont.Json.string "z");;
492492+- : Jsont.json = {"items":["x","z"]}
493493+]x}
494494+495495+The same pointer works whether it targets an existing position or the
496496+append marker - no conditional logic needed.
497497+482498{2 Remove}
483499484500The {!remove} operation deletes a value. It only accepts [nav t] because
485501you can only remove something that exists:
486502487487-{@ocaml[
488488-# let two_fields = parse_json "{\"foo\":\"bar\",\"baz\":\"qux\"}";;
503503+{x@ocaml[
504504+# let two_fields = parse_json {|{"foo":"bar","baz":"qux"}|};;
489505val two_fields : Jsont.json = {"foo":"bar","baz":"qux"}
490506# remove (of_string_nav "/baz") two_fields ;;
491507- : Jsont.json = {"foo":"bar"}
492492-]}
508508+]x}
493509494510For arrays, it removes and shifts:
495511496512{x@ocaml[
497497-# let three_elem = parse_json "{\"foo\":[\"a\",\"b\",\"c\"]}";;
513513+# let three_elem = parse_json {|{"foo":["a","b","c"]}|};;
498514val three_elem : Jsont.json = {"foo":["a","b","c"]}
499515# remove (of_string_nav "/foo/1") three_elem ;;
500516- : Jsont.json = {"foo":["a","c"]}
···516532{2 Move}
517533518534The {!move} operation relocates a value. The source ([from]) must be a [nav t]
519519-(you can only move something that exists), but the destination ([path]) can
520520-be either:
535535+(you can only move something that exists), but the destination ([path])
536536+accepts {!any}:
521537522522-{@ocaml[
523523-# let nested = parse_json "{\"foo\":{\"bar\":\"baz\"},\"qux\":{}}";;
538538+{x@ocaml[
539539+# let nested = parse_json {|{"foo":{"bar":"baz"},"qux":{}}|};;
524540val nested : Jsont.json = {"foo":{"bar":"baz"},"qux":{}}
525525-# move ~from:(of_string_nav "/foo/bar") ~path:(of_string_nav "/qux/thud") nested
526526- ;;
541541+# move ~from:(of_string_nav "/foo/bar") ~path:(of_string "/qux/thud") nested;;
527542- : Jsont.json = {"foo":{},"qux":{"thud":"baz"}}
528528-]}
543543+]x}
529544530545{2 Copy}
531546532547The {!copy} operation duplicates a value (same typing as {!move}):
533548534534-{@ocaml[
535535-# let to_copy = parse_json "{\"foo\":{\"bar\":\"baz\"}}";;
549549+{x@ocaml[
550550+# let to_copy = parse_json {|{"foo":{"bar":"baz"}}|};;
536551val to_copy : Jsont.json = {"foo":{"bar":"baz"}}
537537-# copy ~from:(of_string_nav "/foo/bar") ~path:(of_string_nav "/foo/qux") to_copy
538538- ;;
552552+# copy ~from:(of_string_nav "/foo/bar") ~path:(of_string "/foo/qux") to_copy;;
539553- : Jsont.json = {"foo":{"bar":"baz","qux":"baz"}}
540540-]}
554554+]x}
541555542556{2 Test}
543557···574588575589{@ocaml[
576590# let p = make [mem "a/b"];;
577577-val p : Jsont_pointer.nav Jsont_pointer.t = [Mem "a/b"]
591591+val p : nav t = [Mem "a/b"]
578592# to_string p;;
579593- : string = "/a~1b"
580594# of_string_nav "/a~1b";;
581581-- : Jsont_pointer.nav Jsont_pointer.t = [Mem "a/b"]
595595+- : nav t = [Mem "a/b"]
582596]}
583597584598{2 Escaping in Action}
···657671Here's the RFC example showing the URI fragment forms:
658672659673{ul
660660-{- [""] → [#] → whole document}
661661-{- ["/foo"] → [#/foo] → [["bar", "baz"]]}
662662-{- ["/foo/0"] → [#/foo/0] → ["bar"]}
663663-{- ["/"] → [#/] → [0]}
664664-{- ["/a~1b"] → [#/a~1b] → [1]}
665665-{- ["/c%d"] → [#/c%25d] → [2]}
666666-{- ["/ "] → [#/%20] → [7]}
667667-{- ["/m~0n"] → [#/m~0n] → [8]}
674674+{- [""] -> [#] -> whole document}
675675+{- ["/foo"] -> [#/foo] -> [["bar", "baz"]]}
676676+{- ["/foo/0"] -> [#/foo/0] -> ["bar"]}
677677+{- ["/"] -> [#/] -> [0]}
678678+{- ["/a~1b"] -> [#/a~1b] -> [1]}
679679+{- ["/c%d"] -> [#/c%25d] -> [2]}
680680+{- ["/ "] -> [#/%20] -> [7]}
681681+{- ["/m~0n"] -> [#/m~0n] -> [8]}
668682}
669683670684{1 Building Pointers Programmatically}
···673687674688{@ocaml[
675689# let port_ptr = make [mem "database"; mem "port"];;
676676-val port_ptr : Jsont_pointer.nav Jsont_pointer.t =
677677- [Mem "database"; Mem "port"]
690690+val port_ptr : nav t = [Mem "database"; Mem "port"]
678691# to_string port_ptr;;
679692- : string = "/database/port"
680693]}
···683696684697{@ocaml[
685698# let first_feature_ptr = make [mem "features"; nth 0];;
686686-val first_feature_ptr : Jsont_pointer.nav Jsont_pointer.t =
687687- [Mem "features"; Nth 0]
699699+val first_feature_ptr : nav t = [Mem "features"; Nth 0]
688700# to_string first_feature_ptr;;
689701- : string = "/features/0"
690702]}
···695707696708{@ocaml[
697709# let db_ptr = of_string_nav "/database";;
698698-val db_ptr : Jsont_pointer.nav Jsont_pointer.t = [Mem "database"]
710710+val db_ptr : nav t = [Mem "database"]
699711# let creds_ptr = db_ptr / mem "credentials";;
700700-val creds_ptr : Jsont_pointer.nav Jsont_pointer.t =
701701- [Mem "database"; Mem "credentials"]
712712+val creds_ptr : nav t = [Mem "database"; Mem "credentials"]
702713# let user_ptr = creds_ptr / mem "username";;
703703-val user_ptr : Jsont_pointer.nav Jsont_pointer.t =
704704- [Mem "database"; Mem "credentials"; Mem "username"]
714714+val user_ptr : nav t = [Mem "database"; Mem "credentials"; Mem "username"]
705715# to_string user_ptr;;
706716- : string = "/database/credentials/username"
707717]}
···710720711721{@ocaml[
712722# let base = of_string_nav "/api/v1";;
713713-val base : Jsont_pointer.nav Jsont_pointer.t = [Mem "api"; Mem "v1"]
723723+val base : nav t = [Mem "api"; Mem "v1"]
714724# let endpoint = of_string_nav "/users/0";;
715715-val endpoint : Jsont_pointer.nav Jsont_pointer.t = [Mem "users"; Nth 0]
725725+val endpoint : nav t = [Mem "users"; Nth 0]
716726# to_string (concat base endpoint);;
717727- : string = "/api/v1/users/0"
718728]}
···725735directly to an OCaml type.
726736727737{x@ocaml[
728728-# let config_json = parse_json "{\"database\":{\"host\":\"localhost\",\"port\":5432,\"credentials\":{\"username\":\"admin\",\"password\":\"secret\"}},\"features\":[\"auth\",\"logging\",\"metrics\"]}";;
738738+# let config_json = parse_json {|{
739739+ "database": {
740740+ "host": "localhost",
741741+ "port": 5432,
742742+ "credentials": {"username": "admin", "password": "secret"}
743743+ },
744744+ "features": ["auth", "logging", "metrics"]
745745+ }|};;
729746val config_json : Jsont.json =
730747 {"database":{"host":"localhost","port":5432,"credentials":{"username":"admin","password":"secret"}},"features":["auth","logging","metrics"]}
731748]x}
···735752The {!path} combinator combines pointer navigation with typed decoding:
736753737754{@ocaml[
755755+# let nav = of_string_nav "/database/host";;
756756+val nav : nav t = [Mem "database"; Mem "host"]
738757# let db_host =
739758 Jsont.Json.decode
740740- (path (of_string_nav "/database/host") Jsont.string)
759759+ (path nav Jsont.string)
741760 config_json
742761 |> Result.get_ok;;
743762val db_host : string = "localhost"
···778797You can extract values from deeply nested structures:
779798780799{x@ocaml[
781781-# let org_json = parse_json "{\"organization\":{\"owner\":{\"name\":\"Alice\",\"email\":\"alice@example.com\",\"age\":35},\"members\":[{\"name\":\"Bob\",\"email\":\"bob@example.com\",\"age\":28}]}}";;
800800+# let org_json = parse_json {|{
801801+ "organization": {
802802+ "owner": {"name": "Alice", "email": "alice@example.com", "age": 35},
803803+ "members": [{"name": "Bob", "email": "bob@example.com", "age": 28}]
804804+ }
805805+ }|};;
782806val org_json : Jsont.json =
783807 {"organization":{"owner":{"name":"Alice","email":"alice@example.com","age":35},"members":[{"name":"Bob","email":"bob@example.com","age":28}]}}
784808# Jsont.Json.decode
···818842819843The typed approach catches mismatches at decode time with clear errors.
820844845845+{2 Updates with Polymorphic Pointers}
846846+847847+The {!set} and {!add} functions accept {!any} pointers, which means you can
848848+use the result of {!of_string} directly without pattern matching:
849849+850850+{x@ocaml[
851851+# let tasks = parse_json {|{"tasks":["buy milk"]}|};;
852852+val tasks : Jsont.json = {"tasks":["buy milk"]}
853853+# set (of_string "/tasks/0") tasks ~value:(Jsont.Json.string "buy eggs");;
854854+- : Jsont.json = {"tasks":["buy eggs"]}
855855+# set (of_string "/tasks/-") tasks ~value:(Jsont.Json.string "call mom");;
856856+- : Jsont.json = {"tasks":["buy milk","call mom"]}
857857+]x}
858858+859859+This is useful for implementing JSON Patch ([RFC 6902]) where
860860+operations like ["add"] can target either existing positions or the
861861+append marker. If you need to distinguish between pointer types at runtime,
862862+use {!of_string_kind} which returns a polymorphic variant:
863863+864864+{x@ocaml[
865865+# of_string_kind "/tasks/0";;
866866+- : [ `Append of append t | `Nav of nav t ] = `Nav [Mem "tasks"; Nth 0]
867867+# of_string_kind "/tasks/-";;
868868+- : [ `Append of append t | `Nav of nav t ] = `Append [Mem "tasks"] /-
869869+]x}
870870+821871{1 Summary}
822872823873JSON Pointer (RFC 6901) provides a simple but powerful way to address
···829879{- {b Evaluation}: Tokens navigate through objects (by key) and arrays (by index)}
830880{- {b URI Encoding}: Pointers can be percent-encoded for use in URIs}
831881{- {b Mutations}: Combined with JSON Patch (RFC 6902), pointers enable structured updates}
832832-{- {b Type Safety}: Phantom types ([nav t] vs [append t]) prevent misuse of append pointers with retrieval operations}
882882+{- {b Type Safety}: Phantom types ([nav t] vs [append t]) prevent misuse of append pointers with retrieval operations, while the [any] existential type allows ergonomic use with mutation operations}
833883}
834884835885The [jsont-pointer] library implements all of this with type-safe OCaml
+51-20
src/jsont_pointer.ml
···104104 is_append : bool; (* true if ends with "-" *)
105105}
106106107107+(* Existential wrapper *)
108108+type any = Any : _ t -> any
109109+107110let root = { segments = []; is_append = false }
108111109112let is_root p = p.segments = [] && not p.is_append
···134137135138let indices (type a) (p : a t) = List.map Segment.to_index p.segments
136139140140+(* Coercion and inspection *)
141141+142142+let any (type a) (p : a t) : any = Any p
143143+144144+let is_nav (Any p) = not p.is_append
145145+146146+let to_nav (Any p) =
147147+ if p.is_append then None
148148+ else Some { segments = p.segments; is_append = false }
149149+150150+let to_nav_exn (Any p) =
151151+ if p.is_append then
152152+ Jsont.Error.msgf Jsont.Meta.none
153153+ "JSON Pointer: cannot convert append pointer to nav pointer"
154154+ else
155155+ { segments = p.segments; is_append = false }
156156+137157(* Parsing *)
138158139159let parse_segments s =
···146166 let tokens = String.split_on_char '/' rest in
147167 List.map Segment.of_escaped_string tokens
148168149149-let of_string s : [ `Nav of nav t | `Append of append t ] =
169169+let of_string_kind s : [ `Nav of nav t | `Append of append t ] =
150170 let segments = parse_segments s in
151171 (* Check if ends with "-" *)
152172 match List.rev segments with
···163183 "Invalid JSON Pointer: '-' can only appear at the end";
164184 `Nav { segments; is_append = false }
165185186186+let of_string s : any =
187187+ match of_string_kind s with
188188+ | `Nav p -> Any p
189189+ | `Append p -> Any p
190190+166191let of_string_nav s : nav t =
167167- match of_string s with
192192+ match of_string_kind s with
168193 | `Nav p -> p
169194 | `Append _ ->
170195 Jsont.Error.msgf Jsont.Meta.none
···203228 in
204229 loop 0
205230206206-let of_uri_fragment s = of_string (percent_decode s)
231231+let of_uri_fragment s : any = of_string (percent_decode s)
207232208233let of_uri_fragment_nav s = of_string_nav (percent_decode s)
209234210210-let of_uri_fragment_result s =
235235+let of_uri_fragment_result s : (any, string) result =
211236 try Ok (of_uri_fragment s)
212237 with Jsont.Error e -> Error (Jsont.Error.to_string e)
213238···467492 | None -> error_index_out_of_bounds json n)
468493 ~on_other:(fun () -> error_cannot_navigate json)
469494470470-let set (type a) (p : a t) json ~value =
495495+let set (Any p) json ~value =
471496 eval_set_segments p.segments p.is_append value json
472497473498(* Mutation: add (RFC 6902 semantics) - works with any pointer type *)
···528553 | None -> error_index_out_of_bounds json n)
529554 ~on_other:(fun () -> error_cannot_navigate json)
530555531531-let add (type a) (p : a t) json ~value =
556556+let add (Any p) json ~value =
532557 eval_add_segments p.segments p.is_append value json
533558534559(* Mutation: remove - only for nav pointers *)
···578603579604(* Mutation: move *)
580605581581-let move ~(from : nav t) ~(path : _ t) json =
606606+let move ~(from : nav t) ~(path : any) json =
607607+ let (Any p) = path in
582608 (* Check for cycle: path cannot be a proper prefix of from *)
583609 let from_segs = from.segments in
584584- let path_segs = path.segments in
610610+ let path_segs = p.segments in
585611 let rec is_prefix p1 p2 = match p1, p2 with
586612 | [], _ -> true
587613 | _, [] -> false
588614 | h1 :: t1, h2 :: t2 -> String.equal h1 h2 && is_prefix t1 t2
589615 in
590616 if is_prefix path_segs from_segs &&
591591- not (List.equal String.equal path_segs from_segs && path.is_append = false) then
617617+ not (List.equal String.equal path_segs from_segs && p.is_append = false) then
592618 Jsont.Error.msgf Jsont.Meta.none
593619 "JSON Pointer: move would create cycle (path is prefix of from)";
594620 let value = get from json in
···597623598624(* Mutation: copy *)
599625600600-let copy ~(from : nav t) ~(path : _ t) json =
626626+let copy ~(from : nav t) ~(path : any) json =
601627 let value = get from json in
602628 add path json ~value
603629···608634609635(* Jsont codec *)
610636611611-let jsont : [ `Nav of nav t | `Append of append t ] Jsont.t =
637637+let jsont : any Jsont.t =
612638 let dec _meta s = of_string s in
639639+ let enc (Any p) = to_string p in
640640+ Jsont.Base.string (Jsont.Base.map
641641+ ~kind:"JSON Pointer"
642642+ ~doc:"RFC 6901 JSON Pointer"
643643+ ~dec ~enc ())
644644+645645+let jsont_kind : [ `Nav of nav t | `Append of append t ] Jsont.t =
646646+ let dec _meta s = of_string_kind s in
613647 let enc = function
614648 | `Nav p -> to_string p
615649 | `Append p -> to_string p
616650 in
617651 Jsont.Base.string (Jsont.Base.map
618618- ~kind:"JSON Pointer"
619619- ~doc:"RFC 6901 JSON Pointer"
652652+ ~kind:"JSON Pointer (kind)"
653653+ ~doc:"RFC 6901 JSON Pointer with kind tag"
620654 ~dec ~enc ())
621655622656let jsont_nav : nav t Jsont.t =
···627661 ~doc:"RFC 6901 JSON Pointer (navigation only)"
628662 ~dec ~enc ())
629663630630-let jsont_uri_fragment : [ `Nav of nav t | `Append of append t ] Jsont.t =
664664+let jsont_uri_fragment : any Jsont.t =
631665 let dec _meta s = of_uri_fragment s in
632632- let enc = function
633633- | `Nav p -> to_uri_fragment p
634634- | `Append p -> to_uri_fragment p
635635- in
666666+ let enc (Any p) = to_uri_fragment p in
636667 Jsont.Base.string (Jsont.Base.map
637668 ~kind:"JSON Pointer (URI fragment)"
638669 ~doc:"RFC 6901 JSON Pointer in URI fragment encoding"
···657688 Jsont.map Jsont.json ~dec ~enc:(fun _ ->
658689 Jsont.Error.msgf Jsont.Meta.none "path: encode not supported")
659690660660-let set_path (type a) ?(allow_absent = false) t (p : a t) v =
691691+let set_path ?(allow_absent = false) t (p : any) v =
661692 let encoded = match Jsont.Json.encode' t v with
662693 | Ok json -> json
663694 | Error e -> raise (Jsont.Error e)
···692723 | Ok j -> j
693724 | Error e -> raise (Jsont.Error e)
694725 in
695695- set p json ~value:re_encoded
726726+ set (Any p) json ~value:re_encoded
696727 in
697728 Jsont.map Jsont.json ~dec ~enc:(fun j -> j)
698729
+71-16
src/jsont_pointer.mli
···135135(** Phantom type for pointers ending with [-] (the "after last element"
136136 position). These can only be used with {!add} and {!set}. *)
137137138138+(** {2 Existential wrapper}
139139+140140+ The {!any} type wraps a pointer of unknown phantom type, allowing
141141+ ergonomic use with mutation operations like {!set} and {!add} without
142142+ needing to pattern match on the pointer kind. *)
143143+144144+type any = Any : _ t -> any
145145+(** Existential wrapper for pointers. Use this when you don't need to
146146+ distinguish between navigation and append pointers at the type level,
147147+ such as when using {!set} or {!add} which accept either kind. *)
148148+138149val root : nav t
139150(** [root] is the empty pointer that references the whole document.
140151 In string form this is [""]. *)
···174185 Note: for append pointers, this returns the indices of the path
175186 portion; the [-] (append position) is not represented as an index. *)
176187188188+(** {2:coercion Coercion and inspection} *)
189189+190190+val any : _ t -> any
191191+(** [any p] wraps a typed pointer in the existential {!any} type.
192192+ Use this when you have a [nav t] or [append t] but need an {!any}
193193+ for use with functions like {!set} or {!add}. *)
194194+195195+val is_nav : any -> bool
196196+(** [is_nav p] is [true] if [p] is a navigation pointer (not an append
197197+ pointer ending with [-]). *)
198198+199199+val to_nav : any -> nav t option
200200+(** [to_nav p] returns [Some nav_p] if [p] is a navigation pointer,
201201+ or [None] if it's an append pointer. *)
202202+203203+val to_nav_exn : any -> nav t
204204+(** [to_nav_exn p] returns the navigation pointer if [p] is one.
205205+ @raise Jsont.Error if [p] is an append pointer. *)
206206+177207(** {2:parsing Parsing} *)
178208179179-val of_string : string -> [ `Nav of nav t | `Append of append t ]
209209+val of_string : string -> any
180210(** [of_string s] parses a JSON Pointer from its string representation.
181211182182- Returns [`Nav p] for pointers without [-], or [`Append p] for
183183- pointers ending with [-].
212212+ Returns an {!any} pointer that can be used directly with mutation
213213+ operations like {!set} and {!add}. For retrieval operations like
214214+ {!get}, use {!of_string_nav} instead.
184215185216 The string must be either empty (representing the root) or start
186217 with [/]. Each segment between [/] characters is unescaped as a
···191222 - Invalid escape sequence ([~] not followed by [0] or [1])
192223 - [-] appears in non-final position *)
193224225225+val of_string_kind : string -> [ `Nav of nav t | `Append of append t ]
226226+(** [of_string_kind s] parses a JSON Pointer and returns a tagged variant
227227+ indicating whether it's a navigation or append pointer.
228228+229229+ Use this when you need to handle navigation and append pointers
230230+ differently, or when you need a typed pointer for operations that
231231+ require a specific kind.
232232+233233+ @raise Jsont.Error if [s] has invalid syntax. *)
234234+194235val of_string_nav : string -> nav t
195236(** [of_string_nav s] parses a JSON Pointer that must not contain [-].
237237+238238+ Use this when you need a {!nav} pointer for retrieval operations
239239+ like {!get} or {!find}.
196240197241 @raise Jsont.Error if [s] has invalid syntax or contains [-]. *)
198242199199-val of_string_result : string -> ([ `Nav of nav t | `Append of append t ], string) result
243243+val of_string_result : string -> (any, string) result
200244(** [of_string_result s] is like {!of_string} but returns a result
201245 instead of raising. *)
202246203203-val of_uri_fragment : string -> [ `Nav of nav t | `Append of append t ]
247247+val of_uri_fragment : string -> any
204248(** [of_uri_fragment s] parses a JSON Pointer from URI fragment form.
205249206250 This is like {!of_string} but first percent-decodes the string
···215259216260 @raise Jsont.Error if invalid or contains [-]. *)
217261218218-val of_uri_fragment_result : string -> ([ `Nav of nav t | `Append of append t ], string) result
262262+val of_uri_fragment_result : string -> (any, string) result
219263(** [of_uri_fragment_result s] is like {!of_uri_fragment} but returns
220264 a result instead of raising. *)
221265···296340 All mutation functions return a new JSON value with the modification
297341 applied; they do not mutate the input.
298342299299- Functions that support the [-] token ({!add}, {!set}) accept any
300300- pointer type ([_ t]). Functions that require an existing element
301301- ({!remove}, {!replace}) only accept {!nav} pointers. *)
343343+ Functions that support the [-] token ({!set}, {!add}, {!move}, {!copy})
344344+ accept {!any} pointers, making them easy to use with {!of_string}.
345345+ Functions that require an existing element ({!remove}, {!replace})
346346+ only accept {!nav} pointers. *)
302347303303-val set : _ t -> Jsont.json -> value:Jsont.json -> Jsont.json
348348+val set : any -> Jsont.json -> value:Jsont.json -> Jsont.json
304349(** [set p json ~value] replaces the value at pointer [p] with [value].
305350306351 For {!append} pointers, appends [value] to the end of the array.
307352353353+ This accepts {!any} pointers directly from {!of_string}:
354354+ {[set (of_string "/tasks/-") json ~value:(Jsont.Json.string "new task")]}
355355+308356 @raise Jsont.Error if the pointer doesn't resolve to an existing
309357 location (except for {!append} pointers on arrays). *)
310358311311-val add : _ t -> Jsont.json -> value:Jsont.json -> Jsont.json
359359+val add : any -> Jsont.json -> value:Jsont.json -> Jsont.json
312360(** [add p json ~value] adds [value] at the location specified by [p].
313361314362 The behavior depends on the target:
···342390343391 @raise Jsont.Error if the pointer doesn't resolve to an existing value. *)
344392345345-val move : from:nav t -> path:_ t -> Jsont.json -> Jsont.json
393393+val move : from:nav t -> path:any -> Jsont.json -> Jsont.json
346394(** [move ~from ~path json] moves the value from [from] to [path].
347395348396 This is equivalent to {!remove} at [from] followed by {!add}
···352400 - [from] doesn't resolve to a value
353401 - [path] is a proper prefix of [from] (would create a cycle) *)
354402355355-val copy : from:nav t -> path:_ t -> Jsont.json -> Jsont.json
403403+val copy : from:nav t -> path:any -> Jsont.json -> Jsont.json
356404(** [copy ~from ~path json] copies the value from [from] to [path].
357405358406 This is equivalent to {!get} at [from] followed by {!add}
···374422 These types and functions integrate JSON Pointers with the {!Jsont}
375423 codec system. *)
376424377377-val jsont : [ `Nav of nav t | `Append of append t ] Jsont.t
425425+val jsont : any Jsont.t
378426(** [jsont] is a {!Jsont.t} codec for JSON Pointers.
379427380428 On decode, parses a JSON string as a JSON Pointer using {!of_string}.
381429 On encode, serializes a pointer to a JSON string using {!to_string}. *)
382430431431+val jsont_kind : [ `Nav of nav t | `Append of append t ] Jsont.t
432432+(** [jsont_kind] is a {!Jsont.t} codec for JSON Pointers that preserves
433433+ the pointer kind in the type.
434434+435435+ On decode, parses using {!of_string_kind}.
436436+ On encode, serializes using {!to_string}. *)
437437+383438val jsont_nav : nav t Jsont.t
384439(** [jsont_nav] is a {!Jsont.t} codec for navigation JSON Pointers.
385440386441 On decode, parses using {!of_string_nav} (fails on [-]).
387442 On encode, serializes using {!to_string}. *)
388443389389-val jsont_uri_fragment : [ `Nav of nav t | `Append of append t ] Jsont.t
444444+val jsont_uri_fragment : any Jsont.t
390445(** [jsont_uri_fragment] is like {!jsont} but uses URI fragment encoding.
391446392447 On decode, parses using {!of_uri_fragment}.
···405460406461 This is similar to {!Jsont.path} but uses JSON Pointer syntax. *)
407462408408-val set_path : ?allow_absent:bool -> 'a Jsont.t -> _ t -> 'a -> Jsont.json Jsont.t
463463+val set_path : ?allow_absent:bool -> 'a Jsont.t -> any -> 'a -> Jsont.json Jsont.t
409464(** [set_path t p v] sets the value at pointer [p] to [v] encoded with [t].
410465411466 If [allow_absent] is [true] (default [false]), creates missing