+19
-19
doc/index.html
+19
-19
doc/index.html
···
50
50
"k\"l": 6,
51
51
" ": 7,
52
52
"m~n": 8
53
-
}|};;</x-ocaml></div><p>This document is carefully constructed to exercise various edge cases!</p><h3 id="the-root-pointer"><a href="#the-root-pointer" class="anchor"></a>The Root Pointer</h3><div class="x-ocaml-wrapper"><x-ocaml>get root rfc_example ;;</x-ocaml></div><p>The empty pointer (<code>root</code>) returns the whole document.</p><h3 id="object-member-access"><a href="#object-member-access" class="anchor"></a>Object Member Access</h3><div class="x-ocaml-wrapper"><x-ocaml>get (of_string_nav "/foo") rfc_example ;;</x-ocaml></div><p><code>/foo</code> accesses the member named <code>foo</code>, which is an array.</p><h3 id="array-index-access"><a href="#array-index-access" class="anchor"></a>Array Index Access</h3><div class="x-ocaml-wrapper"><x-ocaml>get (of_string_nav "/foo/0") rfc_example ;;
53
+
}|};;</x-ocaml></div><p>This document is carefully constructed to exercise various edge cases!</p><h3 id="the-root-pointer"><a href="#the-root-pointer" class="anchor"></a>The Root Pointer</h3><div class="x-ocaml-wrapper"><x-ocaml>get root rfc_example ;;</x-ocaml></div><p>The empty pointer (<code>Json_pointer.root</code>) returns the whole document.</p><h3 id="object-member-access"><a href="#object-member-access" class="anchor"></a>Object Member Access</h3><div class="x-ocaml-wrapper"><x-ocaml>get (of_string_nav "/foo") rfc_example ;;</x-ocaml></div><p><code>/foo</code> accesses the member named <code>foo</code>, which is an array.</p><h3 id="array-index-access"><a href="#array-index-access" class="anchor"></a>Array Index Access</h3><div class="x-ocaml-wrapper"><x-ocaml>get (of_string_nav "/foo/0") rfc_example ;;
54
54
get (of_string_nav "/foo/1") rfc_example ;;</x-ocaml></div><p><code>/foo/0</code> first goes to <code>foo</code>, then accesses index 0 of the array.</p><h3 id="empty-string-as-key"><a href="#empty-string-as-key" class="anchor"></a>Empty String as Key</h3><p>JSON allows empty strings as object keys:</p><div class="x-ocaml-wrapper"><x-ocaml>get (of_string_nav "/") rfc_example ;;</x-ocaml></div><p>The pointer <code>/</code> has one token: the empty string. This accesses the member with an empty name.</p><h3 id="keys-with-special-characters"><a href="#keys-with-special-characters" class="anchor"></a>Keys with Special Characters</h3><p>The RFC example includes keys with <code>/</code> and <code>~</code> characters:</p><div class="x-ocaml-wrapper"><x-ocaml>get (of_string_nav "/a~1b") rfc_example ;;</x-ocaml></div><p>The token <code>a~1b</code> refers to the key <code>a/b</code>. We'll explain this escaping <a href="#escaping">below</a>.</p><div class="x-ocaml-wrapper"><x-ocaml>get (of_string_nav "/m~0n") rfc_example ;;</x-ocaml></div><p>The token <code>m~0n</code> refers to the key <code>m~n</code>.</p><p><b>Important</b>: When using the OCaml library programmatically, you don't need to worry about escaping. The <code>Mem</code> variant holds the literal key name:</p><div class="x-ocaml-wrapper"><x-ocaml>let slash_ptr = make [mem "a/b"];;
55
55
to_string slash_ptr;;
56
56
get slash_ptr rfc_example ;;</x-ocaml></div><p>The library escapes it when converting to string.</p><h3 id="other-special-characters-(no-escaping-needed)"><a href="#other-special-characters-(no-escaping-needed)" class="anchor"></a>Other Special Characters (No Escaping Needed)</h3><p>Most characters don't need escaping in JSON Pointer strings:</p><div class="x-ocaml-wrapper"><x-ocaml>get (of_string_nav "/c%d") rfc_example ;;
···
62
62
val find : nav t -> Jsont.json -> Jsont.json option</pre><h3 id="array-index-rules"><a href="#array-index-rules" class="anchor"></a>Array Index Rules</h3><p><a href="https://datatracker.ietf.org/doc/html/rfc6901">RFC 6901</a> has specific rules for array indices. <a href="https://datatracker.ietf.org/doc/html/rfc6901#section-4">Section 4</a> states:</p><p><i>characters comprised of digits <code>...</code> that represent an unsigned base-10 integer value, making the new referenced value the array element with the zero-based index identified by the token</i></p><p>And importantly:</p><p><i>note that leading zeros are not allowed</i></p><div class="x-ocaml-wrapper"><x-ocaml>of_string_nav "/foo/0";;</x-ocaml></div><p>Zero itself is fine.</p><div class="x-ocaml-wrapper"><x-ocaml>of_string_nav "/foo/01";;</x-ocaml></div><p>But <code>01</code> has a leading zero, so it's NOT treated as an array index - it becomes a member name instead. This protects against accidental octal interpretation.</p><h2 id="the-end-of-array-marker:---and-type-safety"><a href="#the-end-of-array-marker:---and-type-safety" class="anchor"></a>The End-of-Array Marker: <code>-</code> and Type Safety</h2><p><a href="https://datatracker.ietf.org/doc/html/rfc6901#section-4">RFC 6901, Section 4</a> introduces a special token:</p><p><i>exactly the single character "-", making the new referenced value the (nonexistent) member after the last array element.</i></p><p>This <code>-</code> marker is unique to JSON Pointer (JSON Path has no equivalent). It's primarily useful for JSON Patch operations (<a href="https://datatracker.ietf.org/doc/html/rfc6902">RFC 6902</a>) to append elements to arrays.</p><h3 id="navigation-vs-append-pointers"><a href="#navigation-vs-append-pointers" class="anchor"></a>Navigation vs Append Pointers</h3><p>The <code>json-pointer</code> library uses <b>phantom types</b> to encode the difference between pointers that can be used for navigation and pointers that target the "append position":</p><pre>type nav (* A pointer to an existing element *)
63
63
type append (* A pointer ending with "-" (append position) *)
64
64
type 'a t (* Pointer with phantom type parameter *)
65
-
type any (* Existential: wraps either nav or append *)</pre><p>When you parse a pointer with <code>of_string</code>, you get an <code>any</code> pointer that can be used directly with mutation operations:</p><div class="x-ocaml-wrapper"><x-ocaml>of_string "/foo/0";;
66
-
of_string "/foo/-";;</x-ocaml></div><p>The <code>-</code> creates an append pointer. The <code>any</code> type wraps either kind, making it ergonomic to use with operations like <code>set</code> and <code>add</code>.</p><h3 id="why-two-pointer-types?"><a href="#why-two-pointer-types?" class="anchor"></a>Why Two Pointer Types?</h3><p>The RFC explains that <code>-</code> refers to a <em>nonexistent</em> position:</p><p><i>Note that the use of the "-" character to index an array will always result in such an error condition because by definition it refers to a nonexistent array element.</i></p><p>So you <b>cannot use <code>get</code> or <code>find</code></b> with an append pointer - it makes no sense to retrieve a value from a position that doesn't exist! The library enforces this:</p><ul><li>Use <code>of_string_nav</code> when you need to call <code>get</code> or <code>find</code></li><li>Use <code>of_string</code> (returns <code>any</code>) for mutation operations</li></ul><p>Mutation operations like <code>add</code> accept <code>any</code> directly:</p><div class="x-ocaml-wrapper"><x-ocaml>let arr_obj = parse_json {|{"foo":["a","b"]}|};;
67
-
add (of_string "/foo/-") arr_obj ~value:(Jsont.Json.string "c");;</x-ocaml></div><p>For retrieval operations, use <code>of_string_nav</code> which ensures the pointer doesn't contain <code>-</code>:</p><div class="x-ocaml-wrapper"><x-ocaml>of_string_nav "/foo/0";;
68
-
of_string_nav "/foo/-";;</x-ocaml></div><h3 id="creating-append-pointers-programmatically"><a href="#creating-append-pointers-programmatically" class="anchor"></a>Creating Append Pointers Programmatically</h3><p>You can convert a navigation pointer to an append pointer using <code>at_end</code>:</p><div class="x-ocaml-wrapper"><x-ocaml>let nav_ptr = of_string_nav "/foo";;
65
+
type any (* Existential: wraps either nav or append *)</pre><p>When you parse a pointer with <code>Json_pointer.of_string</code>, you get an <code>Json_pointer.any</code> pointer that can be used directly with mutation operations:</p><div class="x-ocaml-wrapper"><x-ocaml>of_string "/foo/0";;
66
+
of_string "/foo/-";;</x-ocaml></div><p>The <code>-</code> creates an append pointer. The <code>Json_pointer.any</code> type wraps either kind, making it ergonomic to use with operations like <code>Json_pointer.set</code> and <code>Json_pointer.add</code>.</p><h3 id="why-two-pointer-types?"><a href="#why-two-pointer-types?" class="anchor"></a>Why Two Pointer Types?</h3><p>The RFC explains that <code>-</code> refers to a <em>nonexistent</em> position:</p><p><i>Note that the use of the "-" character to index an array will always result in such an error condition because by definition it refers to a nonexistent array element.</i></p><p>So you <b>cannot use <code>get</code> or <code>find</code></b> with an append pointer - it makes no sense to retrieve a value from a position that doesn't exist! The library enforces this:</p><ul><li>Use <code>Json_pointer.of_string_nav</code> when you need to call <code>Json_pointer.get</code> or <code>Json_pointer.find</code></li><li>Use <code>Json_pointer.of_string</code> (returns <code>Json_pointer.any</code>) for mutation operations</li></ul><p>Mutation operations like <code>Json_pointer.add</code> accept <code>Json_pointer.any</code> directly:</p><div class="x-ocaml-wrapper"><x-ocaml>let arr_obj = parse_json {|{"foo":["a","b"]}|};;
67
+
add (of_string "/foo/-") arr_obj ~value:(Jsont.Json.string "c");;</x-ocaml></div><p>For retrieval operations, use <code>Json_pointer.of_string_nav</code> which ensures the pointer doesn't contain <code>-</code>:</p><div class="x-ocaml-wrapper"><x-ocaml>of_string_nav "/foo/0";;
68
+
of_string_nav "/foo/-";;</x-ocaml></div><h3 id="creating-append-pointers-programmatically"><a href="#creating-append-pointers-programmatically" class="anchor"></a>Creating Append Pointers Programmatically</h3><p>You can convert a navigation pointer to an append pointer using <code>Json_pointer.at_end</code>:</p><div class="x-ocaml-wrapper"><x-ocaml>let nav_ptr = of_string_nav "/foo";;
69
69
let app_ptr = at_end nav_ptr;;
70
-
to_string app_ptr;;</x-ocaml></div><h2 id="mutation-operations"><a href="#mutation-operations" class="anchor"></a>Mutation Operations</h2><p>While <a href="https://datatracker.ietf.org/doc/html/rfc6901">RFC 6901</a> defines JSON Pointer for read-only access, <a href="https://datatracker.ietf.org/doc/html/rfc6902">RFC 6902</a> (JSON Patch) uses JSON Pointer for modifications. The <code>json-pointer</code> library provides these operations.</p><h3 id="add"><a href="#add" class="anchor"></a>Add</h3><p>The <code>add</code> operation inserts a value at a location. It accepts <code>any</code> pointers, so you can use <code>of_string</code> directly:</p><div class="x-ocaml-wrapper"><x-ocaml>let obj = parse_json {|{"foo":"bar"}|};;
71
-
add (of_string "/baz") obj ~value:(Jsont.Json.string "qux");;</x-ocaml></div><p>For arrays, <code>add</code> inserts BEFORE the specified index:</p><div class="x-ocaml-wrapper"><x-ocaml>let arr_obj = parse_json {|{"foo":["a","b"]}|};;
72
-
add (of_string "/foo/1") arr_obj ~value:(Jsont.Json.string "X");;</x-ocaml></div><p>This is where the <code>-</code> marker shines - it appends to the end:</p><div class="x-ocaml-wrapper"><x-ocaml>add (of_string "/foo/-") arr_obj ~value:(Jsont.Json.string "c");;</x-ocaml></div><p>You can also use <code>at_end</code> to create an append pointer programmatically:</p><div class="x-ocaml-wrapper"><x-ocaml>add (any (at_end (of_string_nav "/foo"))) arr_obj ~value:(Jsont.Json.string "c");;</x-ocaml></div><h3 id="ergonomic-mutation-with-any"><a href="#ergonomic-mutation-with-any" class="anchor"></a>Ergonomic Mutation with <code>any</code></h3><p>Since <code>add</code>, <code>set</code>, <code>move</code>, and <code>copy</code> accept <code>any</code> pointers, you can use <code>of_string</code> directly without any pattern matching. This makes JSON Patch implementations straightforward:</p><div class="x-ocaml-wrapper"><x-ocaml>let items = parse_json {|{"items":["x"]}|};;
70
+
to_string app_ptr;;</x-ocaml></div><h2 id="mutation-operations"><a href="#mutation-operations" class="anchor"></a>Mutation Operations</h2><p>While <a href="https://datatracker.ietf.org/doc/html/rfc6901">RFC 6901</a> defines JSON Pointer for read-only access, <a href="https://datatracker.ietf.org/doc/html/rfc6902">RFC 6902</a> (JSON Patch) uses JSON Pointer for modifications. The <code>json-pointer</code> library provides these operations.</p><h3 id="add"><a href="#add" class="anchor"></a>Add</h3><p>The <code>Json_pointer.add</code> operation inserts a value at a location. It accepts <code>Json_pointer.any</code> pointers, so you can use <code>Json_pointer.of_string</code> directly:</p><div class="x-ocaml-wrapper"><x-ocaml>let obj = parse_json {|{"foo":"bar"}|};;
71
+
add (of_string "/baz") obj ~value:(Jsont.Json.string "qux");;</x-ocaml></div><p>For arrays, <code>Json_pointer.add</code> inserts BEFORE the specified index:</p><div class="x-ocaml-wrapper"><x-ocaml>let arr_obj = parse_json {|{"foo":["a","b"]}|};;
72
+
add (of_string "/foo/1") arr_obj ~value:(Jsont.Json.string "X");;</x-ocaml></div><p>This is where the <code>-</code> marker shines - it appends to the end:</p><div class="x-ocaml-wrapper"><x-ocaml>add (of_string "/foo/-") arr_obj ~value:(Jsont.Json.string "c");;</x-ocaml></div><p>You can also use <code>Json_pointer.at_end</code> to create an append pointer programmatically:</p><div class="x-ocaml-wrapper"><x-ocaml>add (any (at_end (of_string_nav "/foo"))) arr_obj ~value:(Jsont.Json.string "c");;</x-ocaml></div><h3 id="ergonomic-mutation-with-any"><a href="#ergonomic-mutation-with-any" class="anchor"></a>Ergonomic Mutation with <code>any</code></h3><p>Since <code>Json_pointer.add</code>, <code>Json_pointer.set</code>, <code>Json_pointer.move</code>, and <code>Json_pointer.copy</code> accept <code>Json_pointer.any</code> pointers, you can use <code>Json_pointer.of_string</code> directly without any pattern matching. This makes JSON Patch implementations straightforward:</p><div class="x-ocaml-wrapper"><x-ocaml>let items = parse_json {|{"items":["x"]}|};;
73
73
add (of_string "/items/0") items ~value:(Jsont.Json.string "y");;
74
-
add (of_string "/items/-") items ~value:(Jsont.Json.string "z");;</x-ocaml></div><p>The same pointer works whether it targets an existing position or the append marker - no conditional logic needed.</p><h3 id="remove"><a href="#remove" class="anchor"></a>Remove</h3><p>The <code>remove</code> operation deletes a value. It only accepts <code>nav t</code> because you can only remove something that exists:</p><div class="x-ocaml-wrapper"><x-ocaml>let two_fields = parse_json {|{"foo":"bar","baz":"qux"}|};;
74
+
add (of_string "/items/-") items ~value:(Jsont.Json.string "z");;</x-ocaml></div><p>The same pointer works whether it targets an existing position or the append marker - no conditional logic needed.</p><h3 id="remove"><a href="#remove" class="anchor"></a>Remove</h3><p>The <code>Json_pointer.remove</code> operation deletes a value. It only accepts <code>nav t</code> because you can only remove something that exists:</p><div class="x-ocaml-wrapper"><x-ocaml>let two_fields = parse_json {|{"foo":"bar","baz":"qux"}|};;
75
75
remove (of_string_nav "/baz") two_fields ;;</x-ocaml></div><p>For arrays, it removes and shifts:</p><div class="x-ocaml-wrapper"><x-ocaml>let three_elem = parse_json {|{"foo":["a","b","c"]}|};;
76
-
remove (of_string_nav "/foo/1") three_elem ;;</x-ocaml></div><h3 id="replace"><a href="#replace" class="anchor"></a>Replace</h3><p>The <code>replace</code> operation updates an existing value:</p><div class="x-ocaml-wrapper"><x-ocaml>replace (of_string_nav "/foo") obj ~value:(Jsont.Json.string "baz")
77
-
;;</x-ocaml></div><p>Unlike <code>add</code>, <code>replace</code> requires the target to already exist (hence <code>nav t</code>). Attempting to replace a nonexistent path raises an error.</p><h3 id="move"><a href="#move" class="anchor"></a>Move</h3><p>The <code>move</code> operation relocates a value. The source (<code>from</code>) must be a <code>nav t</code> (you can only move something that exists), but the destination (<code>path</code>) accepts <code>any</code>:</p><div class="x-ocaml-wrapper"><x-ocaml>let nested = parse_json {|{"foo":{"bar":"baz"},"qux":{}}|};;
78
-
move ~from:(of_string_nav "/foo/bar") ~path:(of_string "/qux/thud") nested;;</x-ocaml></div><h3 id="copy"><a href="#copy" class="anchor"></a>Copy</h3><p>The <code>copy</code> operation duplicates a value (same typing as <code>move</code>):</p><div class="x-ocaml-wrapper"><x-ocaml>let to_copy = parse_json {|{"foo":{"bar":"baz"}}|};;
79
-
copy ~from:(of_string_nav "/foo/bar") ~path:(of_string "/foo/qux") to_copy;;</x-ocaml></div><h3 id="test"><a href="#test" class="anchor"></a>Test</h3><p>The <code>test</code> operation verifies a value (useful in JSON Patch):</p><div class="x-ocaml-wrapper"><x-ocaml>test (of_string_nav "/foo") obj ~expected:(Jsont.Json.string "bar");;
76
+
remove (of_string_nav "/foo/1") three_elem ;;</x-ocaml></div><h3 id="replace"><a href="#replace" class="anchor"></a>Replace</h3><p>The <code>Json_pointer.replace</code> operation updates an existing value:</p><div class="x-ocaml-wrapper"><x-ocaml>replace (of_string_nav "/foo") obj ~value:(Jsont.Json.string "baz")
77
+
;;</x-ocaml></div><p>Unlike <code>Json_pointer.add</code>, <code>Json_pointer.replace</code> requires the target to already exist (hence <code>nav t</code>). Attempting to replace a nonexistent path raises an error.</p><h3 id="move"><a href="#move" class="anchor"></a>Move</h3><p>The <code>Json_pointer.move</code> operation relocates a value. The source (<code>from</code>) must be a <code>nav t</code> (you can only move something that exists), but the destination (<code>path</code>) accepts <code>Json_pointer.any</code>:</p><div class="x-ocaml-wrapper"><x-ocaml>let nested = parse_json {|{"foo":{"bar":"baz"},"qux":{}}|};;
78
+
move ~from:(of_string_nav "/foo/bar") ~path:(of_string "/qux/thud") nested;;</x-ocaml></div><h3 id="copy"><a href="#copy" class="anchor"></a>Copy</h3><p>The <code>Json_pointer.copy</code> operation duplicates a value (same typing as <code>Json_pointer.move</code>):</p><div class="x-ocaml-wrapper"><x-ocaml>let to_copy = parse_json {|{"foo":{"bar":"baz"}}|};;
79
+
copy ~from:(of_string_nav "/foo/bar") ~path:(of_string "/foo/qux") to_copy;;</x-ocaml></div><h3 id="test"><a href="#test" class="anchor"></a>Test</h3><p>The <code>Json_pointer.test</code> operation verifies a value (useful in JSON Patch):</p><div class="x-ocaml-wrapper"><x-ocaml>test (of_string_nav "/foo") obj ~expected:(Jsont.Json.string "bar");;
80
80
test (of_string_nav "/foo") obj ~expected:(Jsont.Json.string "wrong");;</x-ocaml></div><h2 id="escaping"><a href="#escaping" class="anchor"></a>Escaping Special Characters</h2><p><a href="https://datatracker.ietf.org/doc/html/rfc6901#section-3">RFC 6901, Section 3</a> explains the escaping rules:</p><p><i>Because the characters '~' (%x7E) and '/' (%x2F) have special meanings in JSON Pointer, '~' needs to be encoded as '~0' and '/' needs to be encoded as '~1' when these characters appear in a reference token.</i></p><p>Why these specific characters?</p><ul><li><code>/</code> separates tokens, so it must be escaped inside a token</li><li><code>~</code> is the escape character itself, so it must also be escaped</li></ul><p>The escape sequences are:</p><ul><li><code>~0</code> represents <code>~</code> (tilde)</li><li><code>~1</code> represents <code>/</code> (forward slash)</li></ul><h3 id="the-library-handles-escaping-automatically"><a href="#the-library-handles-escaping-automatically" class="anchor"></a>The Library Handles Escaping Automatically</h3><p><b>Important</b>: When using <code>json-pointer</code> programmatically, you rarely need to think about escaping. The <code>Mem</code> variant stores unescaped strings, and escaping happens automatically during serialization:</p><div class="x-ocaml-wrapper"><x-ocaml>let p = make [mem "a/b"];;
81
81
to_string p;;
82
-
of_string_nav "/a~1b";;</x-ocaml></div><h3 id="escaping-in-action"><a href="#escaping-in-action" class="anchor"></a>Escaping in Action</h3><p>The <code>Token</code> module exposes the escaping functions:</p><div class="x-ocaml-wrapper"><x-ocaml>Token.escape "hello";;
82
+
of_string_nav "/a~1b";;</x-ocaml></div><h3 id="escaping-in-action"><a href="#escaping-in-action" class="anchor"></a>Escaping in Action</h3><p>The <code>Json_pointer.Token</code> module exposes the escaping functions:</p><div class="x-ocaml-wrapper"><x-ocaml>Token.escape "hello";;
83
83
Token.escape "a/b";;
84
84
Token.escape "a~b";;
85
85
Token.escape "~/";;</x-ocaml></div><h3 id="unescaping"><a href="#unescaping" class="anchor"></a>Unescaping</h3><p>And the reverse process:</p><div class="x-ocaml-wrapper"><x-ocaml>Token.unescape "a~1b";;
···
87
87
to_uri_fragment (of_string_nav "/a~1b");;
88
88
to_uri_fragment (of_string_nav "/c%d");;
89
89
to_uri_fragment (of_string_nav "/ ");;</x-ocaml></div><p>The <code>%</code> character must be percent-encoded as <code>%25</code> in URIs, and spaces become <code>%20</code>.</p><p>Here's the RFC example showing the URI fragment forms:</p><ul><li><code>""</code> -> <code>#</code> -> whole document</li><li><code>"/foo"</code> -> <code>#/foo</code> -> <code>["bar", "baz"]</code></li><li><code>"/foo/0"</code> -> <code>#/foo/0</code> -> <code>"bar"</code></li><li><code>"/"</code> -> <code>#/</code> -> <code>0</code></li><li><code>"/a~1b"</code> -> <code>#/a~1b</code> -> <code>1</code></li><li><code>"/c%d"</code> -> <code>#/c%25d</code> -> <code>2</code></li><li><code>"/ "</code> -> <code>#/%20</code> -> <code>7</code></li><li><code>"/m~0n"</code> -> <code>#/m~0n</code> -> <code>8</code></li></ul><h2 id="building-pointers-programmatically"><a href="#building-pointers-programmatically" class="anchor"></a>Building Pointers Programmatically</h2><p>Instead of parsing strings, you can build pointers from indices:</p><div class="x-ocaml-wrapper"><x-ocaml>let port_ptr = make [mem "database"; mem "port"];;
90
-
to_string port_ptr;;</x-ocaml></div><p>For array access, use the <code>nth</code> helper:</p><div class="x-ocaml-wrapper"><x-ocaml>let first_feature_ptr = make [mem "features"; nth 0];;
91
-
to_string first_feature_ptr;;</x-ocaml></div><h3 id="pointer-navigation"><a href="#pointer-navigation" class="anchor"></a>Pointer Navigation</h3><p>You can build pointers incrementally using the <code>/</code> operator (or <code>append_index</code>):</p><div class="x-ocaml-wrapper"><x-ocaml>let db_ptr = of_string_nav "/database";;
90
+
to_string port_ptr;;</x-ocaml></div><p>For array access, use the <code>Json_pointer.nth</code> helper:</p><div class="x-ocaml-wrapper"><x-ocaml>let first_feature_ptr = make [mem "features"; nth 0];;
91
+
to_string first_feature_ptr;;</x-ocaml></div><h3 id="pointer-navigation"><a href="#pointer-navigation" class="anchor"></a>Pointer Navigation</h3><p>You can build pointers incrementally using the <code>/</code> operator (or <code>Json_pointer.append_index</code>):</p><div class="x-ocaml-wrapper"><x-ocaml>let db_ptr = of_string_nav "/database";;
92
92
let creds_ptr = db_ptr / mem "credentials";;
93
93
let user_ptr = creds_ptr / mem "username";;
94
94
to_string user_ptr;;</x-ocaml></div><p>Or concatenate two pointers:</p><div class="x-ocaml-wrapper"><x-ocaml>let base = of_string_nav "/api/v1";;
···
100
100
"credentials": {"username": "admin", "password": "secret"}
101
101
},
102
102
"features": ["auth", "logging", "metrics"]
103
-
}|};;</x-ocaml></div><h3 id="typed-access-with-path"><a href="#typed-access-with-path" class="anchor"></a>Typed Access with <code>path</code></h3><p>The <code>path</code> combinator combines pointer navigation with typed decoding:</p><div class="x-ocaml-wrapper"><x-ocaml>let nav = of_string_nav "/database/host";;
103
+
}|};;</x-ocaml></div><h3 id="typed-access-with-path"><a href="#typed-access-with-path" class="anchor"></a>Typed Access with <code>path</code></h3><p>The <code>Json_pointer.path</code> combinator combines pointer navigation with typed decoding:</p><div class="x-ocaml-wrapper"><x-ocaml>let nav = of_string_nav "/database/host";;
104
104
let db_host =
105
105
Jsont.Json.decode
106
106
(path nav Jsont.string)
···
138
138
Jsont.Json.decode
139
139
(path (of_string_nav "/database/port") Jsont.int)
140
140
config_json
141
-
|> Result.get_ok;;</x-ocaml></div><p>The typed approach catches mismatches at decode time with clear errors.</p><h3 id="updates-with-polymorphic-pointers"><a href="#updates-with-polymorphic-pointers" class="anchor"></a>Updates with Polymorphic Pointers</h3><p>The <code>set</code> and <code>add</code> functions accept <code>any</code> pointers, which means you can use the result of <code>of_string</code> directly without pattern matching:</p><div class="x-ocaml-wrapper"><x-ocaml>let tasks = parse_json {|{"tasks":["buy milk"]}|};;
141
+
|> Result.get_ok;;</x-ocaml></div><p>The typed approach catches mismatches at decode time with clear errors.</p><h3 id="updates-with-polymorphic-pointers"><a href="#updates-with-polymorphic-pointers" class="anchor"></a>Updates with Polymorphic Pointers</h3><p>The <code>Json_pointer.set</code> and <code>Json_pointer.add</code> functions accept <code>Json_pointer.any</code> pointers, which means you can use the result of <code>Json_pointer.of_string</code> directly without pattern matching:</p><div class="x-ocaml-wrapper"><x-ocaml>let tasks = parse_json {|{"tasks":["buy milk"]}|};;
142
142
set (of_string "/tasks/0") tasks ~value:(Jsont.Json.string "buy eggs");;
143
-
set (of_string "/tasks/-") tasks ~value:(Jsont.Json.string "call mom");;</x-ocaml></div><p>This is useful for implementing JSON Patch (<a href="https://datatracker.ietf.org/doc/html/rfc6902">RFC 6902</a>) where operations like <code>"add"</code> can target either existing positions or the append marker. If you need to distinguish between pointer types at runtime, use <code>of_string_kind</code> which returns a polymorphic variant:</p><div class="x-ocaml-wrapper"><x-ocaml>of_string_kind "/tasks/0";;
143
+
set (of_string "/tasks/-") tasks ~value:(Jsont.Json.string "call mom");;</x-ocaml></div><p>This is useful for implementing JSON Patch (<a href="https://datatracker.ietf.org/doc/html/rfc6902">RFC 6902</a>) where operations like <code>"add"</code> can target either existing positions or the append marker. If you need to distinguish between pointer types at runtime, use <code>Json_pointer.of_string_kind</code> which returns a polymorphic variant:</p><div class="x-ocaml-wrapper"><x-ocaml>of_string_kind "/tasks/0";;
144
144
of_string_kind "/tasks/-";;</x-ocaml></div><h2 id="summary"><a href="#summary" class="anchor"></a>Summary</h2><p>JSON Pointer (<a href="https://datatracker.ietf.org/doc/html/rfc6901">RFC 6901</a>) provides a simple but powerful way to address values within JSON documents:</p><ol><li><b>Syntax</b>: Pointers are strings of <code>/</code>-separated reference tokens</li><li><b>Escaping</b>: Use <code>~0</code> for <code>~</code> and <code>~1</code> for <code>/</code> in tokens (handled automatically by the library)</li><li><b>Evaluation</b>: Tokens navigate through objects (by key) and arrays (by index)</li><li><b>URI Encoding</b>: Pointers can be percent-encoded for use in URIs</li><li><b>Mutations</b>: Combined with JSON Patch (<a href="https://datatracker.ietf.org/doc/html/rfc6902">RFC 6902</a>), pointers enable structured updates</li><li><b>Type Safety</b>: Phantom types (<code>nav t</code> vs <code>append t</code>) prevent misuse of append pointers with retrieval operations, while the <code>any</code> existential type allows ergonomic use with mutation operations</li></ol><p>The <code>json-pointer</code> library implements all of this with type-safe OCaml interfaces, integration with the <code>jsont</code> codec system, and proper error handling for malformed pointers and missing values.</p><h3 id="key-points-on-json-pointer-vs-json-path"><a href="#key-points-on-json-pointer-vs-json-path" class="anchor"></a>Key Points on JSON Pointer vs JSON Path</h3><ul><li><b>JSON Pointer</b> addresses a <em>single</em> location (like a file path)</li><li><b>JSON Path</b> queries for <em>multiple</em> values (like a search)</li><li>The <code>-</code> token is unique to JSON Pointer - it means "append position" for arrays</li><li>The library uses phantom types to enforce that <code>-</code> (append) pointers cannot be used with <code>get</code>/<code>find</code></li></ul></div>
145
145
</body></html>
+20
-20
doc/tutorial.html
+20
-20
doc/tutorial.html
···
44
44
val rfc_example : Jsont.json =
45
45
{"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}</code></pre><p>This document is carefully constructed to exercise various edge cases!</p><h3 id="the-root-pointer"><a href="#the-root-pointer" class="anchor"></a>The Root Pointer</h3><pre class="language-ocaml"><code># get root rfc_example ;;
46
46
- : Jsont.json =
47
-
{"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}</code></pre><p>The empty pointer (<code>root</code>) returns the whole document.</p><h3 id="object-member-access"><a href="#object-member-access" class="anchor"></a>Object Member Access</h3><pre class="language-ocaml"><code># get (of_string_nav "/foo") rfc_example ;;
47
+
{"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}</code></pre><p>The empty pointer (<code>Json_pointer.root</code>) returns the whole document.</p><h3 id="object-member-access"><a href="#object-member-access" class="anchor"></a>Object Member Access</h3><pre class="language-ocaml"><code># get (of_string_nav "/foo") rfc_example ;;
48
48
- : Jsont.json = ["bar","baz"]</code></pre><p><code>/foo</code> accesses the member named <code>foo</code>, which is an array.</p><h3 id="array-index-access"><a href="#array-index-access" class="anchor"></a>Array Index Access</h3><pre class="language-ocaml"><code># get (of_string_nav "/foo/0") rfc_example ;;
49
49
- : Jsont.json = "bar"
50
50
# get (of_string_nav "/foo/1") rfc_example ;;
···
77
77
- : nav t = [Mem "foo"; Mem "01"]</code></pre><p>But <code>01</code> has a leading zero, so it's NOT treated as an array index - it becomes a member name instead. This protects against accidental octal interpretation.</p><h2 id="the-end-of-array-marker:---and-type-safety"><a href="#the-end-of-array-marker:---and-type-safety" class="anchor"></a>The End-of-Array Marker: <code>-</code> and Type Safety</h2><p><a href="https://datatracker.ietf.org/doc/html/rfc6901#section-4">RFC 6901, Section 4</a> introduces a special token:</p><p><i>exactly the single character "-", making the new referenced value the (nonexistent) member after the last array element.</i></p><p>This <code>-</code> marker is unique to JSON Pointer (JSON Path has no equivalent). It's primarily useful for JSON Patch operations (<a href="https://datatracker.ietf.org/doc/html/rfc6902">RFC 6902</a>) to append elements to arrays.</p><h3 id="navigation-vs-append-pointers"><a href="#navigation-vs-append-pointers" class="anchor"></a>Navigation vs Append Pointers</h3><p>The <code>json-pointer</code> library uses <b>phantom types</b> to encode the difference between pointers that can be used for navigation and pointers that target the "append position":</p><pre>type nav (* A pointer to an existing element *)
78
78
type append (* A pointer ending with "-" (append position) *)
79
79
type 'a t (* Pointer with phantom type parameter *)
80
-
type any (* Existential: wraps either nav or append *)</pre><p>When you parse a pointer with <code>of_string</code>, you get an <code>any</code> pointer that can be used directly with mutation operations:</p><pre class="language-ocaml"><code># of_string "/foo/0";;
80
+
type any (* Existential: wraps either nav or append *)</pre><p>When you parse a pointer with <code>Json_pointer.of_string</code>, you get an <code>Json_pointer.any</code> pointer that can be used directly with mutation operations:</p><pre class="language-ocaml"><code># of_string "/foo/0";;
81
81
- : any = Any <abstr>
82
82
# of_string "/foo/-";;
83
-
- : any = Any <abstr></code></pre><p>The <code>-</code> creates an append pointer. The <code>any</code> type wraps either kind, making it ergonomic to use with operations like <code>set</code> and <code>add</code>.</p><h3 id="why-two-pointer-types?"><a href="#why-two-pointer-types?" class="anchor"></a>Why Two Pointer Types?</h3><p>The RFC explains that <code>-</code> refers to a <em>nonexistent</em> position:</p><p><i>Note that the use of the "-" character to index an array will always result in such an error condition because by definition it refers to a nonexistent array element.</i></p><p>So you <b>cannot use <code>get</code> or <code>find</code></b> with an append pointer - it makes no sense to retrieve a value from a position that doesn't exist! The library enforces this:</p><ul><li>Use <code>of_string_nav</code> when you need to call <code>get</code> or <code>find</code></li><li>Use <code>of_string</code> (returns <code>any</code>) for mutation operations</li></ul><p>Mutation operations like <code>add</code> accept <code>any</code> directly:</p><pre class="language-ocaml"><code># let arr_obj = parse_json {|{"foo":["a","b"]}|};;
83
+
- : any = Any <abstr></code></pre><p>The <code>-</code> creates an append pointer. The <code>Json_pointer.any</code> type wraps either kind, making it ergonomic to use with operations like <code>Json_pointer.set</code> and <code>Json_pointer.add</code>.</p><h3 id="why-two-pointer-types?"><a href="#why-two-pointer-types?" class="anchor"></a>Why Two Pointer Types?</h3><p>The RFC explains that <code>-</code> refers to a <em>nonexistent</em> position:</p><p><i>Note that the use of the "-" character to index an array will always result in such an error condition because by definition it refers to a nonexistent array element.</i></p><p>So you <b>cannot use <code>get</code> or <code>find</code></b> with an append pointer - it makes no sense to retrieve a value from a position that doesn't exist! The library enforces this:</p><ul><li>Use <code>Json_pointer.of_string_nav</code> when you need to call <code>Json_pointer.get</code> or <code>Json_pointer.find</code></li><li>Use <code>Json_pointer.of_string</code> (returns <code>Json_pointer.any</code>) for mutation operations</li></ul><p>Mutation operations like <code>Json_pointer.add</code> accept <code>Json_pointer.any</code> directly:</p><pre class="language-ocaml"><code># let arr_obj = parse_json {|{"foo":["a","b"]}|};;
84
84
val arr_obj : Jsont.json = {"foo":["a","b"]}
85
85
# add (of_string "/foo/-") arr_obj ~value:(Jsont.Json.string "c");;
86
-
- : Jsont.json = {"foo":["a","b","c"]}</code></pre><p>For retrieval operations, use <code>of_string_nav</code> which ensures the pointer doesn't contain <code>-</code>:</p><pre class="language-ocaml"><code># of_string_nav "/foo/0";;
86
+
- : Jsont.json = {"foo":["a","b","c"]}</code></pre><p>For retrieval operations, use <code>Json_pointer.of_string_nav</code> which ensures the pointer doesn't contain <code>-</code>:</p><pre class="language-ocaml"><code># of_string_nav "/foo/0";;
87
87
- : nav t = [Mem "foo"; Nth 0]
88
88
# of_string_nav "/foo/-";;
89
89
Exception:
90
-
Jsont.Error Invalid JSON Pointer: '-' not allowed in navigation pointer.</code></pre><h3 id="creating-append-pointers-programmatically"><a href="#creating-append-pointers-programmatically" class="anchor"></a>Creating Append Pointers Programmatically</h3><p>You can convert a navigation pointer to an append pointer using <code>at_end</code>:</p><pre class="language-ocaml"><code># let nav_ptr = of_string_nav "/foo";;
90
+
Jsont.Error Invalid JSON Pointer: '-' not allowed in navigation pointer.</code></pre><h3 id="creating-append-pointers-programmatically"><a href="#creating-append-pointers-programmatically" class="anchor"></a>Creating Append Pointers Programmatically</h3><p>You can convert a navigation pointer to an append pointer using <code>Json_pointer.at_end</code>:</p><pre class="language-ocaml"><code># let nav_ptr = of_string_nav "/foo";;
91
91
val nav_ptr : nav t = [Mem "foo"]
92
92
# let app_ptr = at_end nav_ptr;;
93
93
val app_ptr : append t = [Mem "foo"] /-
94
94
# to_string app_ptr;;
95
-
- : string = "/foo/-"</code></pre><h2 id="mutation-operations"><a href="#mutation-operations" class="anchor"></a>Mutation Operations</h2><p>While <a href="https://datatracker.ietf.org/doc/html/rfc6901">RFC 6901</a> defines JSON Pointer for read-only access, <a href="https://datatracker.ietf.org/doc/html/rfc6902">RFC 6902</a> (JSON Patch) uses JSON Pointer for modifications. The <code>json-pointer</code> library provides these operations.</p><h3 id="add"><a href="#add" class="anchor"></a>Add</h3><p>The <code>add</code> operation inserts a value at a location. It accepts <code>any</code> pointers, so you can use <code>of_string</code> directly:</p><pre class="language-ocaml"><code># let obj = parse_json {|{"foo":"bar"}|};;
95
+
- : string = "/foo/-"</code></pre><h2 id="mutation-operations"><a href="#mutation-operations" class="anchor"></a>Mutation Operations</h2><p>While <a href="https://datatracker.ietf.org/doc/html/rfc6901">RFC 6901</a> defines JSON Pointer for read-only access, <a href="https://datatracker.ietf.org/doc/html/rfc6902">RFC 6902</a> (JSON Patch) uses JSON Pointer for modifications. The <code>json-pointer</code> library provides these operations.</p><h3 id="add"><a href="#add" class="anchor"></a>Add</h3><p>The <code>Json_pointer.add</code> operation inserts a value at a location. It accepts <code>Json_pointer.any</code> pointers, so you can use <code>Json_pointer.of_string</code> directly:</p><pre class="language-ocaml"><code># let obj = parse_json {|{"foo":"bar"}|};;
96
96
val obj : Jsont.json = {"foo":"bar"}
97
97
# add (of_string "/baz") obj ~value:(Jsont.Json.string "qux");;
98
-
- : Jsont.json = {"foo":"bar","baz":"qux"}</code></pre><p>For arrays, <code>add</code> inserts BEFORE the specified index:</p><pre class="language-ocaml"><code># let arr_obj = parse_json {|{"foo":["a","b"]}|};;
98
+
- : Jsont.json = {"foo":"bar","baz":"qux"}</code></pre><p>For arrays, <code>Json_pointer.add</code> inserts BEFORE the specified index:</p><pre class="language-ocaml"><code># let arr_obj = parse_json {|{"foo":["a","b"]}|};;
99
99
val arr_obj : Jsont.json = {"foo":["a","b"]}
100
100
# add (of_string "/foo/1") arr_obj ~value:(Jsont.Json.string "X");;
101
101
- : Jsont.json = {"foo":["a","X","b"]}</code></pre><p>This is where the <code>-</code> marker shines - it appends to the end:</p><pre class="language-ocaml"><code># add (of_string "/foo/-") arr_obj ~value:(Jsont.Json.string "c");;
102
-
- : Jsont.json = {"foo":["a","b","c"]}</code></pre><p>You can also use <code>at_end</code> to create an append pointer programmatically:</p><pre class="language-ocaml"><code># add (any (at_end (of_string_nav "/foo"))) arr_obj ~value:(Jsont.Json.string "c");;
103
-
- : Jsont.json = {"foo":["a","b","c"]}</code></pre><h3 id="ergonomic-mutation-with-any"><a href="#ergonomic-mutation-with-any" class="anchor"></a>Ergonomic Mutation with <code>any</code></h3><p>Since <code>add</code>, <code>set</code>, <code>move</code>, and <code>copy</code> accept <code>any</code> pointers, you can use <code>of_string</code> directly without any pattern matching. This makes JSON Patch implementations straightforward:</p><pre class="language-ocaml"><code># let items = parse_json {|{"items":["x"]}|};;
102
+
- : Jsont.json = {"foo":["a","b","c"]}</code></pre><p>You can also use <code>Json_pointer.at_end</code> to create an append pointer programmatically:</p><pre class="language-ocaml"><code># add (any (at_end (of_string_nav "/foo"))) arr_obj ~value:(Jsont.Json.string "c");;
103
+
- : Jsont.json = {"foo":["a","b","c"]}</code></pre><h3 id="ergonomic-mutation-with-any"><a href="#ergonomic-mutation-with-any" class="anchor"></a>Ergonomic Mutation with <code>any</code></h3><p>Since <code>Json_pointer.add</code>, <code>Json_pointer.set</code>, <code>Json_pointer.move</code>, and <code>Json_pointer.copy</code> accept <code>Json_pointer.any</code> pointers, you can use <code>Json_pointer.of_string</code> directly without any pattern matching. This makes JSON Patch implementations straightforward:</p><pre class="language-ocaml"><code># let items = parse_json {|{"items":["x"]}|};;
104
104
val items : Jsont.json = {"items":["x"]}
105
105
# add (of_string "/items/0") items ~value:(Jsont.Json.string "y");;
106
106
- : Jsont.json = {"items":["y","x"]}
107
107
# add (of_string "/items/-") items ~value:(Jsont.Json.string "z");;
108
-
- : Jsont.json = {"items":["x","z"]}</code></pre><p>The same pointer works whether it targets an existing position or the append marker - no conditional logic needed.</p><h3 id="remove"><a href="#remove" class="anchor"></a>Remove</h3><p>The <code>remove</code> operation deletes a value. It only accepts <code>nav t</code> because you can only remove something that exists:</p><pre class="language-ocaml"><code># let two_fields = parse_json {|{"foo":"bar","baz":"qux"}|};;
108
+
- : Jsont.json = {"items":["x","z"]}</code></pre><p>The same pointer works whether it targets an existing position or the append marker - no conditional logic needed.</p><h3 id="remove"><a href="#remove" class="anchor"></a>Remove</h3><p>The <code>Json_pointer.remove</code> operation deletes a value. It only accepts <code>nav t</code> because you can only remove something that exists:</p><pre class="language-ocaml"><code># let two_fields = parse_json {|{"foo":"bar","baz":"qux"}|};;
109
109
val two_fields : Jsont.json = {"foo":"bar","baz":"qux"}
110
110
# remove (of_string_nav "/baz") two_fields ;;
111
111
- : Jsont.json = {"foo":"bar"}</code></pre><p>For arrays, it removes and shifts:</p><pre class="language-ocaml"><code># let three_elem = parse_json {|{"foo":["a","b","c"]}|};;
112
112
val three_elem : Jsont.json = {"foo":["a","b","c"]}
113
113
# remove (of_string_nav "/foo/1") three_elem ;;
114
-
- : Jsont.json = {"foo":["a","c"]}</code></pre><h3 id="replace"><a href="#replace" class="anchor"></a>Replace</h3><p>The <code>replace</code> operation updates an existing value:</p><pre class="language-ocaml"><code># replace (of_string_nav "/foo") obj ~value:(Jsont.Json.string "baz")
114
+
- : Jsont.json = {"foo":["a","c"]}</code></pre><h3 id="replace"><a href="#replace" class="anchor"></a>Replace</h3><p>The <code>Json_pointer.replace</code> operation updates an existing value:</p><pre class="language-ocaml"><code># replace (of_string_nav "/foo") obj ~value:(Jsont.Json.string "baz")
115
115
;;
116
-
- : Jsont.json = {"foo":"baz"}</code></pre><p>Unlike <code>add</code>, <code>replace</code> requires the target to already exist (hence <code>nav t</code>). Attempting to replace a nonexistent path raises an error.</p><h3 id="move"><a href="#move" class="anchor"></a>Move</h3><p>The <code>move</code> operation relocates a value. The source (<code>from</code>) must be a <code>nav t</code> (you can only move something that exists), but the destination (<code>path</code>) accepts <code>any</code>:</p><pre class="language-ocaml"><code># let nested = parse_json {|{"foo":{"bar":"baz"},"qux":{}}|};;
116
+
- : Jsont.json = {"foo":"baz"}</code></pre><p>Unlike <code>Json_pointer.add</code>, <code>Json_pointer.replace</code> requires the target to already exist (hence <code>nav t</code>). Attempting to replace a nonexistent path raises an error.</p><h3 id="move"><a href="#move" class="anchor"></a>Move</h3><p>The <code>Json_pointer.move</code> operation relocates a value. The source (<code>from</code>) must be a <code>nav t</code> (you can only move something that exists), but the destination (<code>path</code>) accepts <code>Json_pointer.any</code>:</p><pre class="language-ocaml"><code># let nested = parse_json {|{"foo":{"bar":"baz"},"qux":{}}|};;
117
117
val nested : Jsont.json = {"foo":{"bar":"baz"},"qux":{}}
118
118
# move ~from:(of_string_nav "/foo/bar") ~path:(of_string "/qux/thud") nested;;
119
-
- : Jsont.json = {"foo":{},"qux":{"thud":"baz"}}</code></pre><h3 id="copy"><a href="#copy" class="anchor"></a>Copy</h3><p>The <code>copy</code> operation duplicates a value (same typing as <code>move</code>):</p><pre class="language-ocaml"><code># let to_copy = parse_json {|{"foo":{"bar":"baz"}}|};;
119
+
- : Jsont.json = {"foo":{},"qux":{"thud":"baz"}}</code></pre><h3 id="copy"><a href="#copy" class="anchor"></a>Copy</h3><p>The <code>Json_pointer.copy</code> operation duplicates a value (same typing as <code>Json_pointer.move</code>):</p><pre class="language-ocaml"><code># let to_copy = parse_json {|{"foo":{"bar":"baz"}}|};;
120
120
val to_copy : Jsont.json = {"foo":{"bar":"baz"}}
121
121
# copy ~from:(of_string_nav "/foo/bar") ~path:(of_string "/foo/qux") to_copy;;
122
-
- : Jsont.json = {"foo":{"bar":"baz","qux":"baz"}}</code></pre><h3 id="test"><a href="#test" class="anchor"></a>Test</h3><p>The <code>test</code> operation verifies a value (useful in JSON Patch):</p><pre class="language-ocaml"><code># test (of_string_nav "/foo") obj ~expected:(Jsont.Json.string "bar");;
122
+
- : Jsont.json = {"foo":{"bar":"baz","qux":"baz"}}</code></pre><h3 id="test"><a href="#test" class="anchor"></a>Test</h3><p>The <code>Json_pointer.test</code> operation verifies a value (useful in JSON Patch):</p><pre class="language-ocaml"><code># test (of_string_nav "/foo") obj ~expected:(Jsont.Json.string "bar");;
123
123
- : bool = true
124
124
# test (of_string_nav "/foo") obj ~expected:(Jsont.Json.string "wrong");;
125
125
- : bool = false</code></pre><h2 id="escaping"><a href="#escaping" class="anchor"></a>Escaping Special Characters</h2><p><a href="https://datatracker.ietf.org/doc/html/rfc6901#section-3">RFC 6901, Section 3</a> explains the escaping rules:</p><p><i>Because the characters '~' (%x7E) and '/' (%x2F) have special meanings in JSON Pointer, '~' needs to be encoded as '~0' and '/' needs to be encoded as '~1' when these characters appear in a reference token.</i></p><p>Why these specific characters?</p><ul><li><code>/</code> separates tokens, so it must be escaped inside a token</li><li><code>~</code> is the escape character itself, so it must also be escaped</li></ul><p>The escape sequences are:</p><ul><li><code>~0</code> represents <code>~</code> (tilde)</li><li><code>~1</code> represents <code>/</code> (forward slash)</li></ul><h3 id="the-library-handles-escaping-automatically"><a href="#the-library-handles-escaping-automatically" class="anchor"></a>The Library Handles Escaping Automatically</h3><p><b>Important</b>: When using <code>json-pointer</code> programmatically, you rarely need to think about escaping. The <code>Mem</code> variant stores unescaped strings, and escaping happens automatically during serialization:</p><pre class="language-ocaml"><code># let p = make [mem "a/b"];;
···
127
127
# to_string p;;
128
128
- : string = "/a~1b"
129
129
# of_string_nav "/a~1b";;
130
-
- : nav t = [Mem "a/b"]</code></pre><h3 id="escaping-in-action"><a href="#escaping-in-action" class="anchor"></a>Escaping in Action</h3><p>The <code>Token</code> module exposes the escaping functions:</p><pre class="language-ocaml"><code># Token.escape "hello";;
130
+
- : nav t = [Mem "a/b"]</code></pre><h3 id="escaping-in-action"><a href="#escaping-in-action" class="anchor"></a>Escaping in Action</h3><p>The <code>Json_pointer.Token</code> module exposes the escaping functions:</p><pre class="language-ocaml"><code># Token.escape "hello";;
131
131
- : string = "hello"
132
132
# Token.escape "a/b";;
133
133
- : string = "a~1b"
···
148
148
- : string = "/%20"</code></pre><p>The <code>%</code> character must be percent-encoded as <code>%25</code> in URIs, and spaces become <code>%20</code>.</p><p>Here's the RFC example showing the URI fragment forms:</p><ul><li><code>""</code> -> <code>#</code> -> whole document</li><li><code>"/foo"</code> -> <code>#/foo</code> -> <code>["bar", "baz"]</code></li><li><code>"/foo/0"</code> -> <code>#/foo/0</code> -> <code>"bar"</code></li><li><code>"/"</code> -> <code>#/</code> -> <code>0</code></li><li><code>"/a~1b"</code> -> <code>#/a~1b</code> -> <code>1</code></li><li><code>"/c%d"</code> -> <code>#/c%25d</code> -> <code>2</code></li><li><code>"/ "</code> -> <code>#/%20</code> -> <code>7</code></li><li><code>"/m~0n"</code> -> <code>#/m~0n</code> -> <code>8</code></li></ul><h2 id="building-pointers-programmatically"><a href="#building-pointers-programmatically" class="anchor"></a>Building Pointers Programmatically</h2><p>Instead of parsing strings, you can build pointers from indices:</p><pre class="language-ocaml"><code># let port_ptr = make [mem "database"; mem "port"];;
149
149
val port_ptr : nav t = [Mem "database"; Mem "port"]
150
150
# to_string port_ptr;;
151
-
- : string = "/database/port"</code></pre><p>For array access, use the <code>nth</code> helper:</p><pre class="language-ocaml"><code># let first_feature_ptr = make [mem "features"; nth 0];;
151
+
- : string = "/database/port"</code></pre><p>For array access, use the <code>Json_pointer.nth</code> helper:</p><pre class="language-ocaml"><code># let first_feature_ptr = make [mem "features"; nth 0];;
152
152
val first_feature_ptr : nav t = [Mem "features"; Nth 0]
153
153
# to_string first_feature_ptr;;
154
-
- : string = "/features/0"</code></pre><h3 id="pointer-navigation"><a href="#pointer-navigation" class="anchor"></a>Pointer Navigation</h3><p>You can build pointers incrementally using the <code>/</code> operator (or <code>append_index</code>):</p><pre class="language-ocaml"><code># let db_ptr = of_string_nav "/database";;
154
+
- : string = "/features/0"</code></pre><h3 id="pointer-navigation"><a href="#pointer-navigation" class="anchor"></a>Pointer Navigation</h3><p>You can build pointers incrementally using the <code>/</code> operator (or <code>Json_pointer.append_index</code>):</p><pre class="language-ocaml"><code># let db_ptr = of_string_nav "/database";;
155
155
val db_ptr : nav t = [Mem "database"]
156
156
# let creds_ptr = db_ptr / mem "credentials";;
157
157
val creds_ptr : nav t = [Mem "database"; Mem "credentials"]
···
172
172
"features": ["auth", "logging", "metrics"]
173
173
}|};;
174
174
val config_json : Jsont.json =
175
-
{"database":{"host":"localhost","port":5432,"credentials":{"username":"admin","password":"secret"}},"features":["auth","logging","metrics"]}</code></pre><h3 id="typed-access-with-path"><a href="#typed-access-with-path" class="anchor"></a>Typed Access with <code>path</code></h3><p>The <code>path</code> combinator combines pointer navigation with typed decoding:</p><pre class="language-ocaml"><code># let nav = of_string_nav "/database/host";;
175
+
{"database":{"host":"localhost","port":5432,"credentials":{"username":"admin","password":"secret"}},"features":["auth","logging","metrics"]}</code></pre><h3 id="typed-access-with-path"><a href="#typed-access-with-path" class="anchor"></a>Typed Access with <code>path</code></h3><p>The <code>Json_pointer.path</code> combinator combines pointer navigation with typed decoding:</p><pre class="language-ocaml"><code># let nav = of_string_nav "/database/host";;
176
176
val nav : nav t = [Mem "database"; Mem "host"]
177
177
# let db_host =
178
178
Jsont.Json.decode
···
221
221
(path (of_string_nav "/database/port") Jsont.int)
222
222
config_json
223
223
|> Result.get_ok;;
224
-
val typed_port : int = 5432</code></pre><p>The typed approach catches mismatches at decode time with clear errors.</p><h3 id="updates-with-polymorphic-pointers"><a href="#updates-with-polymorphic-pointers" class="anchor"></a>Updates with Polymorphic Pointers</h3><p>The <code>set</code> and <code>add</code> functions accept <code>any</code> pointers, which means you can use the result of <code>of_string</code> directly without pattern matching:</p><pre class="language-ocaml"><code># let tasks = parse_json {|{"tasks":["buy milk"]}|};;
224
+
val typed_port : int = 5432</code></pre><p>The typed approach catches mismatches at decode time with clear errors.</p><h3 id="updates-with-polymorphic-pointers"><a href="#updates-with-polymorphic-pointers" class="anchor"></a>Updates with Polymorphic Pointers</h3><p>The <code>Json_pointer.set</code> and <code>Json_pointer.add</code> functions accept <code>Json_pointer.any</code> pointers, which means you can use the result of <code>Json_pointer.of_string</code> directly without pattern matching:</p><pre class="language-ocaml"><code># let tasks = parse_json {|{"tasks":["buy milk"]}|};;
225
225
val tasks : Jsont.json = {"tasks":["buy milk"]}
226
226
# set (of_string "/tasks/0") tasks ~value:(Jsont.Json.string "buy eggs");;
227
227
- : Jsont.json = {"tasks":["buy eggs"]}
228
228
# set (of_string "/tasks/-") tasks ~value:(Jsont.Json.string "call mom");;
229
-
- : Jsont.json = {"tasks":["buy milk","call mom"]}</code></pre><p>This is useful for implementing JSON Patch (<a href="https://datatracker.ietf.org/doc/html/rfc6902">RFC 6902</a>) where operations like <code>"add"</code> can target either existing positions or the append marker. If you need to distinguish between pointer types at runtime, use <code>of_string_kind</code> which returns a polymorphic variant:</p><pre class="language-ocaml"><code># of_string_kind "/tasks/0";;
229
+
- : Jsont.json = {"tasks":["buy milk","call mom"]}</code></pre><p>This is useful for implementing JSON Patch (<a href="https://datatracker.ietf.org/doc/html/rfc6902">RFC 6902</a>) where operations like <code>"add"</code> can target either existing positions or the append marker. If you need to distinguish between pointer types at runtime, use <code>Json_pointer.of_string_kind</code> which returns a polymorphic variant:</p><pre class="language-ocaml"><code># of_string_kind "/tasks/0";;
230
230
- : [ `Append of append t | `Nav of nav t ] = `Nav [Mem "tasks"; Nth 0]
231
231
# of_string_kind "/tasks/-";;
232
232
- : [ `Append of append t | `Nav of nav t ] = `Append [Mem "tasks"] /-</code></pre><h2 id="summary"><a href="#summary" class="anchor"></a>Summary</h2><p>JSON Pointer (<a href="https://datatracker.ietf.org/doc/html/rfc6901">RFC 6901</a>) provides a simple but powerful way to address values within JSON documents:</p><ol><li><b>Syntax</b>: Pointers are strings of <code>/</code>-separated reference tokens</li><li><b>Escaping</b>: Use <code>~0</code> for <code>~</code> and <code>~1</code> for <code>/</code> in tokens (handled automatically by the library)</li><li><b>Evaluation</b>: Tokens navigate through objects (by key) and arrays (by index)</li><li><b>URI Encoding</b>: Pointers can be percent-encoded for use in URIs</li><li><b>Mutations</b>: Combined with JSON Patch (<a href="https://datatracker.ietf.org/doc/html/rfc6902">RFC 6902</a>), pointers enable structured updates</li><li><b>Type Safety</b>: Phantom types (<code>nav t</code> vs <code>append t</code>) prevent misuse of append pointers with retrieval operations, while the <code>any</code> existential type allows ergonomic use with mutation operations</li></ol><p>The <code>json-pointer</code> library implements all of this with type-safe OCaml interfaces, integration with the <code>jsont</code> codec system, and proper error handling for malformed pointers and missing values.</p><h3 id="key-points-on-json-pointer-vs-json-path"><a href="#key-points-on-json-pointer-vs-json-path" class="anchor"></a>Key Points on JSON Pointer vs JSON Path</h3><ul><li><b>JSON Pointer</b> addresses a <em>single</em> location (like a file path)</li><li><b>JSON Path</b> queries for <em>multiple</em> values (like a search)</li><li>The <code>-</code> token is unique to JSON Pointer - it means "append position" for arrays</li><li>The library uses phantom types to enforce that <code>-</code> (append) pointers cannot be used with <code>get</code>/<code>find</code></li></ul></div></body></html>
+29
-29
doc/tutorial.mld
+29
-29
doc/tutorial.mld
···
205
205
{"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}
206
206
]}
207
207
208
-
The empty pointer ({!root}) returns the whole document.
208
+
The empty pointer ({!Json_pointer.root}) returns the whole document.
209
209
210
210
{2 Object Member Access}
211
211
···
376
376
type any (* Existential: wraps either nav or append *)
377
377
v}
378
378
379
-
When you parse a pointer with {!of_string}, you get an {!any} pointer
379
+
When you parse a pointer with {!Json_pointer.of_string}, you get an {!type:Json_pointer.any} pointer
380
380
that can be used directly with mutation operations:
381
381
382
382
{@ocaml[
···
386
386
- : any = Any <abstr>
387
387
]}
388
388
389
-
The [-] creates an append pointer. The {!any} type wraps either kind,
390
-
making it ergonomic to use with operations like {!set} and {!add}.
389
+
The [-] creates an append pointer. The {!type:Json_pointer.any} type wraps either kind,
390
+
making it ergonomic to use with operations like {!Json_pointer.set} and {!Json_pointer.add}.
391
391
392
392
{2 Why Two Pointer Types?}
393
393
···
400
400
So you {b cannot use [get] or [find]} with an append pointer - it makes
401
401
no sense to retrieve a value from a position that doesn't exist! The
402
402
library enforces this:
403
-
- Use {!of_string_nav} when you need to call {!get} or {!find}
404
-
- Use {!of_string} (returns {!any}) for mutation operations
403
+
- Use {!Json_pointer.of_string_nav} when you need to call {!Json_pointer.get} or {!Json_pointer.find}
404
+
- Use {!Json_pointer.of_string} (returns {!type:Json_pointer.any}) for mutation operations
405
405
406
-
Mutation operations like {!add} accept {!any} directly:
406
+
Mutation operations like {!Json_pointer.add} accept {!type:Json_pointer.any} directly:
407
407
408
408
{x@ocaml[
409
409
# let arr_obj = parse_json {|{"foo":["a","b"]}|};;
···
412
412
- : Jsont.json = {"foo":["a","b","c"]}
413
413
]x}
414
414
415
-
For retrieval operations, use {!of_string_nav} which ensures the pointer
415
+
For retrieval operations, use {!Json_pointer.of_string_nav} which ensures the pointer
416
416
doesn't contain [-]:
417
417
418
418
{@ocaml[
···
425
425
426
426
{2 Creating Append Pointers Programmatically}
427
427
428
-
You can convert a navigation pointer to an append pointer using {!at_end}:
428
+
You can convert a navigation pointer to an append pointer using {!Json_pointer.at_end}:
429
429
430
430
{@ocaml[
431
431
# let nav_ptr = of_string_nav "/foo";;
···
444
444
445
445
{2 Add}
446
446
447
-
The {!add} operation inserts a value at a location. It accepts {!any}
448
-
pointers, so you can use {!of_string} directly:
447
+
The {!Json_pointer.add} operation inserts a value at a location. It accepts {!type:Json_pointer.any}
448
+
pointers, so you can use {!Json_pointer.of_string} directly:
449
449
450
450
{x@ocaml[
451
451
# let obj = parse_json {|{"foo":"bar"}|};;
···
454
454
- : Jsont.json = {"foo":"bar","baz":"qux"}
455
455
]x}
456
456
457
-
For arrays, {!add} inserts BEFORE the specified index:
457
+
For arrays, {!Json_pointer.add} inserts BEFORE the specified index:
458
458
459
459
{x@ocaml[
460
460
# let arr_obj = parse_json {|{"foo":["a","b"]}|};;
···
470
470
- : Jsont.json = {"foo":["a","b","c"]}
471
471
]x}
472
472
473
-
You can also use {!at_end} to create an append pointer programmatically:
473
+
You can also use {!Json_pointer.at_end} to create an append pointer programmatically:
474
474
475
475
{x@ocaml[
476
476
# add (any (at_end (of_string_nav "/foo"))) arr_obj ~value:(Jsont.Json.string "c");;
···
479
479
480
480
{2 Ergonomic Mutation with [any]}
481
481
482
-
Since {!add}, {!set}, {!move}, and {!copy} accept {!any} pointers, you can
483
-
use {!of_string} directly without any pattern matching. This makes JSON
482
+
Since {!Json_pointer.add}, {!Json_pointer.set}, {!Json_pointer.move}, and {!Json_pointer.copy} accept {!type:Json_pointer.any} pointers, you can
483
+
use {!Json_pointer.of_string} directly without any pattern matching. This makes JSON
484
484
Patch implementations straightforward:
485
485
486
486
{x@ocaml[
···
497
497
498
498
{2 Remove}
499
499
500
-
The {!remove} operation deletes a value. It only accepts [nav t] because
500
+
The {!Json_pointer.remove} operation deletes a value. It only accepts [nav t] because
501
501
you can only remove something that exists:
502
502
503
503
{x@ocaml[
···
518
518
519
519
{2 Replace}
520
520
521
-
The {!replace} operation updates an existing value:
521
+
The {!Json_pointer.replace} operation updates an existing value:
522
522
523
523
{@ocaml[
524
524
# replace (of_string_nav "/foo") obj ~value:(Jsont.Json.string "baz")
···
526
526
- : Jsont.json = {"foo":"baz"}
527
527
]}
528
528
529
-
Unlike {!add}, {!replace} requires the target to already exist (hence [nav t]).
529
+
Unlike {!Json_pointer.add}, {!Json_pointer.replace} requires the target to already exist (hence [nav t]).
530
530
Attempting to replace a nonexistent path raises an error.
531
531
532
532
{2 Move}
533
533
534
-
The {!move} operation relocates a value. The source ([from]) must be a [nav t]
534
+
The {!Json_pointer.move} operation relocates a value. The source ([from]) must be a [nav t]
535
535
(you can only move something that exists), but the destination ([path])
536
-
accepts {!any}:
536
+
accepts {!type:Json_pointer.any}:
537
537
538
538
{x@ocaml[
539
539
# let nested = parse_json {|{"foo":{"bar":"baz"},"qux":{}}|};;
···
544
544
545
545
{2 Copy}
546
546
547
-
The {!copy} operation duplicates a value (same typing as {!move}):
547
+
The {!Json_pointer.copy} operation duplicates a value (same typing as {!Json_pointer.move}):
548
548
549
549
{x@ocaml[
550
550
# let to_copy = parse_json {|{"foo":{"bar":"baz"}}|};;
···
555
555
556
556
{2 Test}
557
557
558
-
The {!test} operation verifies a value (useful in JSON Patch):
558
+
The {!Json_pointer.test} operation verifies a value (useful in JSON Patch):
559
559
560
560
{@ocaml[
561
561
# test (of_string_nav "/foo") obj ~expected:(Jsont.Json.string "bar");;
···
597
597
598
598
{2 Escaping in Action}
599
599
600
-
The {!Token} module exposes the escaping functions:
600
+
The {!Json_pointer.Token} module exposes the escaping functions:
601
601
602
602
{@ocaml[
603
603
# Token.escape "hello";;
···
692
692
- : string = "/database/port"
693
693
]}
694
694
695
-
For array access, use the {!nth} helper:
695
+
For array access, use the {!Json_pointer.nth} helper:
696
696
697
697
{@ocaml[
698
698
# let first_feature_ptr = make [mem "features"; nth 0];;
···
703
703
704
704
{2 Pointer Navigation}
705
705
706
-
You can build pointers incrementally using the [/] operator (or {!append_index}):
706
+
You can build pointers incrementally using the [/] operator (or {!Json_pointer.append_index}):
707
707
708
708
{@ocaml[
709
709
# let db_ptr = of_string_nav "/database";;
···
749
749
750
750
{2 Typed Access with [path]}
751
751
752
-
The {!path} combinator combines pointer navigation with typed decoding:
752
+
The {!Json_pointer.path} combinator combines pointer navigation with typed decoding:
753
753
754
754
{@ocaml[
755
755
# let nav = of_string_nav "/database/host";;
···
844
844
845
845
{2 Updates with Polymorphic Pointers}
846
846
847
-
The {!set} and {!add} functions accept {!any} pointers, which means you can
848
-
use the result of {!of_string} directly without pattern matching:
847
+
The {!Json_pointer.set} and {!Json_pointer.add} functions accept {!type:Json_pointer.any} pointers, which means you can
848
+
use the result of {!Json_pointer.of_string} directly without pattern matching:
849
849
850
850
{x@ocaml[
851
851
# let tasks = parse_json {|{"tasks":["buy milk"]}|};;
···
859
859
This is useful for implementing JSON Patch ({{:https://datatracker.ietf.org/doc/html/rfc6902}RFC 6902}) where
860
860
operations like ["add"] can target either existing positions or the
861
861
append marker. If you need to distinguish between pointer types at runtime,
862
-
use {!of_string_kind} which returns a polymorphic variant:
862
+
use {!Json_pointer.of_string_kind} which returns a polymorphic variant:
863
863
864
864
{x@ocaml[
865
865
# of_string_kind "/tasks/0";;
+26
-26
src/json_pointer.mli
+26
-26
src/json_pointer.mli
···
77
77
(** [unescape s] unescapes a JSON Pointer reference token.
78
78
Specifically, [~1] becomes [/] and [~0] becomes [~].
79
79
80
-
@raise Jsont.Error if [s] contains invalid escape sequences
80
+
@raise Jsont.Error.Error if [s] contains invalid escape sequences
81
81
(a [~] not followed by [0] or [1]). *)
82
82
end
83
83
···
137
137
138
138
(** {2 Existential wrapper}
139
139
140
-
The {!any} type wraps a pointer of unknown phantom type, allowing
140
+
The {!type:any} type wraps a pointer of unknown phantom type, allowing
141
141
ergonomic use with mutation operations like {!set} and {!add} without
142
142
needing to pattern match on the pointer kind. *)
143
143
···
188
188
(** {2:coercion Coercion and inspection} *)
189
189
190
190
val any : _ t -> any
191
-
(** [any p] wraps a typed pointer in the existential {!any} type.
192
-
Use this when you have a [nav t] or [append t] but need an {!any}
191
+
(** [any p] wraps a typed pointer in the existential {!type:any} type.
192
+
Use this when you have a [nav t] or [append t] but need an {!type:any}
193
193
for use with functions like {!set} or {!add}. *)
194
194
195
195
val is_nav : any -> bool
···
202
202
203
203
val to_nav_exn : any -> nav t
204
204
(** [to_nav_exn p] returns the navigation pointer if [p] is one.
205
-
@raise Jsont.Error if [p] is an append pointer. *)
205
+
@raise Jsont.Error.Error if [p] is an append pointer. *)
206
206
207
207
(** {2:parsing Parsing} *)
208
208
209
209
val of_string : string -> any
210
210
(** [of_string s] parses a JSON Pointer from its string representation.
211
211
212
-
Returns an {!any} pointer that can be used directly with mutation
212
+
Returns an {!type:any} pointer that can be used directly with mutation
213
213
operations like {!set} and {!add}. For retrieval operations like
214
214
{!get}, use {!of_string_nav} instead.
215
215
···
217
217
with [/]. Each segment between [/] characters is unescaped as a
218
218
reference token.
219
219
220
-
@raise Jsont.Error if [s] has invalid syntax:
220
+
@raise Jsont.Error.Error if [s] has invalid syntax:
221
221
- Non-empty string not starting with [/]
222
222
- Invalid escape sequence ([~] not followed by [0] or [1])
223
223
- [-] appears in non-final position *)
···
230
230
differently, or when you need a typed pointer for operations that
231
231
require a specific kind.
232
232
233
-
@raise Jsont.Error if [s] has invalid syntax. *)
233
+
@raise Jsont.Error.Error if [s] has invalid syntax. *)
234
234
235
235
val of_string_nav : string -> nav t
236
236
(** [of_string_nav s] parses a JSON Pointer that must not contain [-].
···
238
238
Use this when you need a {!nav} pointer for retrieval operations
239
239
like {!get} or {!find}.
240
240
241
-
@raise Jsont.Error if [s] has invalid syntax or contains [-]. *)
241
+
@raise Jsont.Error.Error if [s] has invalid syntax or contains [-]. *)
242
242
243
243
val of_string_result : string -> (any, string) result
244
244
(** [of_string_result s] is like {!of_string} but returns a result
···
251
251
according to {{:https://www.rfc-editor.org/rfc/rfc3986}RFC 3986}.
252
252
The leading [#] should {b not} be included in [s].
253
253
254
-
@raise Jsont.Error on invalid syntax or invalid percent-encoding. *)
254
+
@raise Jsont.Error.Error on invalid syntax or invalid percent-encoding. *)
255
255
256
256
val of_uri_fragment_nav : string -> nav t
257
257
(** [of_uri_fragment_nav s] is like {!of_uri_fragment} but requires
258
258
the pointer to not contain [-].
259
259
260
-
@raise Jsont.Error if invalid or contains [-]. *)
260
+
@raise Jsont.Error.Error if invalid or contains [-]. *)
261
261
262
262
val of_uri_fragment_result : string -> (any, string) result
263
263
(** [of_uri_fragment_result s] is like {!of_uri_fragment} but returns
···
310
310
311
311
(** {1 Evaluation}
312
312
313
-
These functions evaluate a JSON Pointer against a {!Jsont.json} value
313
+
These functions evaluate a JSON Pointer against a {!type:Jsont.json} value
314
314
to retrieve the referenced value. They only accept {!nav} pointers
315
315
since {!append} pointers refer to nonexistent positions. *)
316
316
317
317
val get : nav t -> Jsont.json -> Jsont.json
318
318
(** [get p json] retrieves the value at pointer [p] in [json].
319
319
320
-
@raise Jsont.Error if:
320
+
@raise Jsont.Error.Error if:
321
321
- The pointer references a nonexistent object member
322
322
- The pointer references an out-of-bounds array index
323
323
- An index type doesn't match the JSON value (e.g., [Nth]
···
332
332
333
333
(** {1 Mutation}
334
334
335
-
These functions modify a {!Jsont.json} value at a location specified
335
+
These functions modify a {!type:Jsont.json} value at a location specified
336
336
by a JSON Pointer. They are designed to support
337
337
{{:https://www.rfc-editor.org/rfc/rfc6902}RFC 6902 JSON Patch}
338
338
operations.
···
341
341
applied; they do not mutate the input.
342
342
343
343
Functions that support the [-] token ({!set}, {!add}, {!move}, {!copy})
344
-
accept {!any} pointers, making them easy to use with {!of_string}.
344
+
accept {!type:any} pointers, making them easy to use with {!of_string}.
345
345
Functions that require an existing element ({!remove}, {!replace})
346
346
only accept {!nav} pointers. *)
347
347
···
350
350
351
351
For {!append} pointers, appends [value] to the end of the array.
352
352
353
-
This accepts {!any} pointers directly from {!of_string}:
353
+
This accepts {!type:any} pointers directly from {!of_string}:
354
354
{[set (of_string "/tasks/-") json ~value:(Jsont.Json.string "new task")]}
355
355
356
-
@raise Jsont.Error if the pointer doesn't resolve to an existing
356
+
@raise Jsont.Error.Error if the pointer doesn't resolve to an existing
357
357
location (except for {!append} pointers on arrays). *)
358
358
359
359
val add : any -> Jsont.json -> value:Jsont.json -> Jsont.json
···
368
368
valid (0 to length inclusive).}
369
369
{- For {!append} pointers: Appends [value] to the array.}}
370
370
371
-
@raise Jsont.Error if:
371
+
@raise Jsont.Error.Error if:
372
372
- The parent of the target location doesn't exist
373
373
- An array index is out of bounds (except for {!append} pointers)
374
374
- The parent is not an object or array *)
···
379
379
For objects, removes the member. For arrays, removes the element
380
380
and shifts subsequent elements.
381
381
382
-
@raise Jsont.Error if:
382
+
@raise Jsont.Error.Error if:
383
383
- [p] is the root (cannot remove the root)
384
384
- The pointer doesn't resolve to an existing value *)
385
385
···
388
388
389
389
Unlike {!add}, this requires the target to exist.
390
390
391
-
@raise Jsont.Error if the pointer doesn't resolve to an existing value. *)
391
+
@raise Jsont.Error.Error if the pointer doesn't resolve to an existing value. *)
392
392
393
393
val move : from:nav t -> path:any -> Jsont.json -> Jsont.json
394
394
(** [move ~from ~path json] moves the value from [from] to [path].
···
396
396
This is equivalent to {!remove} at [from] followed by {!add}
397
397
at [path] with the removed value.
398
398
399
-
@raise Jsont.Error if:
399
+
@raise Jsont.Error.Error if:
400
400
- [from] doesn't resolve to a value
401
401
- [path] is a proper prefix of [from] (would create a cycle) *)
402
402
···
406
406
This is equivalent to {!get} at [from] followed by {!add}
407
407
at [path] with the retrieved value.
408
408
409
-
@raise Jsont.Error if [from] doesn't resolve to a value. *)
409
+
@raise Jsont.Error.Error if [from] doesn't resolve to a value. *)
410
410
411
411
val test : nav t -> Jsont.json -> expected:Jsont.json -> bool
412
412
(** [test p json ~expected] tests if the value at [p] equals [expected].
···
511
511
The syntax is the same as RFC 6901 JSON Pointer, except [*] is allowed
512
512
as a reference token for array mapping.
513
513
514
-
@raise Jsont.Error if [s] has invalid syntax. *)
514
+
@raise Jsont.Error.Error if [s] has invalid syntax. *)
515
515
516
516
val of_string_result : string -> (t, string) result
517
517
(** [of_string_result s] is like {!of_string} but returns a result. *)
···
528
528
For [*] tokens on arrays, maps through all elements and collects results.
529
529
Results that are arrays are flattened into the output.
530
530
531
-
@raise Jsont.Error if:
531
+
@raise Jsont.Error.Error if:
532
532
- A standard token doesn't resolve (member not found, index out of bounds)
533
533
- [*] is used on a non-array value
534
534
- [-] appears in the pointer (not supported in JMAP extended pointers) *)
···
561
561
(Jsont.list Jsont.string)
562
562
]}
563
563
564
-
@raise Jsont.Error if the pointer fails to resolve (and no [absent])
564
+
@raise Jsont.Error.Error if the pointer fails to resolve (and no [absent])
565
565
or if decoding with [codec] fails. *)
566
566
567
567
val path_list : t -> 'a Jsont.t -> 'a list Jsont.t
···
576
576
Jmap.path (Jmap.of_string "/list/*/id") (Jsont.list Jsont.string)
577
577
]}
578
578
579
-
@raise Jsont.Error if pointer resolution fails, the result is not an array,
579
+
@raise Jsont.Error.Error if pointer resolution fails, the result is not an array,
580
580
or any element fails to decode. *)
581
581
end
+4
-4
src/top/json_pointer_top.mli
+4
-4
src/top/json_pointer_top.mli
···
1
-
(** Toplevel printers for {!Json_pointer}, {!Jsont.json}, and {!Jsont.Error.t}.
1
+
(** Toplevel printers for {!Json_pointer}, {!type:Jsont.json}, and {!Jsont.Error.t}.
2
2
3
3
Printers are automatically installed when the library is loaded:
4
4
{[
···
12
12
]}
13
13
14
14
JSON values will display as formatted JSON strings:
15
-
{[
15
+
{v
16
16
# Jsont_bytesrw.decode_string Jsont.json {|{"foo": [1, 2]}|};;
17
17
- : Jsont.json = {"foo": [1, 2]}
18
-
]}
18
+
v}
19
19
20
20
And errors will display as readable messages:
21
21
{[
···
32
32
Suitable for use with [#install_printer]. *)
33
33
34
34
val json_printer : Format.formatter -> Jsont.json -> unit
35
-
(** [json_printer] formats a {!Jsont.json} value as a human-readable
35
+
(** [json_printer] formats a {!type:Jsont.json} value as a human-readable
36
36
JSON string. Suitable for use with [#install_printer]. *)
37
37
38
38
val error_printer : Format.formatter -> Jsont.Error.t -> unit