···5050 "k\"l": 6,
5151 " ": 7,
5252 "m~n": 8
5353- }|};;</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 ;;
5353+ }|};;</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 ;;
5454get (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"];;
5555to_string slash_ptr;;
5656get 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 ;;
···6262val 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 *)
6363type append (* A pointer ending with "-" (append position) *)
6464type 'a t (* Pointer with phantom type parameter *)
6565-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";;
6666-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"]}|};;
6767-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";;
6868-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";;
6565+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";;
6666+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"]}|};;
6767+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";;
6868+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";;
6969let app_ptr = at_end nav_ptr;;
7070-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"}|};;
7171-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"]}|};;
7272-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"]}|};;
7070+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"}|};;
7171+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"]}|};;
7272+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"]}|};;
7373add (of_string "/items/0") items ~value:(Jsont.Json.string "y");;
7474-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"}|};;
7474+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"}|};;
7575remove (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"]}|};;
7676-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")
7777- ;;</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":{}}|};;
7878-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"}}|};;
7979-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");;
7676+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")
7777+ ;;</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":{}}|};;
7878+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"}}|};;
7979+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");;
8080test (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"];;
8181to_string p;;
8282-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";;
8282+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";;
8383Token.escape "a/b";;
8484Token.escape "a~b";;
8585Token.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";;
···8787to_uri_fragment (of_string_nav "/a~1b");;
8888to_uri_fragment (of_string_nav "/c%d");;
8989to_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"];;
9090-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];;
9191-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";;
9090+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];;
9191+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";;
9292let creds_ptr = db_ptr / mem "credentials";;
9393let user_ptr = creds_ptr / mem "username";;
9494to_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";;
···100100 "credentials": {"username": "admin", "password": "secret"}
101101 },
102102 "features": ["auth", "logging", "metrics"]
103103- }|};;</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";;
103103+ }|};;</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";;
104104let db_host =
105105 Jsont.Json.decode
106106 (path nav Jsont.string)
···138138 Jsont.Json.decode
139139 (path (of_string_nav "/database/port") Jsont.int)
140140 config_json
141141- |> 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"]}|};;
141141+ |> 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"]}|};;
142142set (of_string "/tasks/0") tasks ~value:(Jsont.Json.string "buy eggs");;
143143-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";;
143143+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";;
144144of_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>
145145</body></html>
+20-20
doc/tutorial.html
···4444val rfc_example : Jsont.json =
4545 {"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 ;;
4646- : Jsont.json =
4747-{"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 ;;
4747+{"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 ;;
4848- : 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 ;;
4949- : Jsont.json = "bar"
5050# get (of_string_nav "/foo/1") rfc_example ;;
···7777- : 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 *)
7878type append (* A pointer ending with "-" (append position) *)
7979type 'a t (* Pointer with phantom type parameter *)
8080-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";;
8080+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";;
8181- : any = Any <abstr>
8282# of_string "/foo/-";;
8383-- : 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"]}|};;
8383+- : 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"]}|};;
8484val arr_obj : Jsont.json = {"foo":["a","b"]}
8585# add (of_string "/foo/-") arr_obj ~value:(Jsont.Json.string "c");;
8686-- : 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";;
8686+- : 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";;
8787- : nav t = [Mem "foo"; Nth 0]
8888# of_string_nav "/foo/-";;
8989Exception:
9090-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";;
9090+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";;
9191val nav_ptr : nav t = [Mem "foo"]
9292# let app_ptr = at_end nav_ptr;;
9393val app_ptr : append t = [Mem "foo"] /-
9494# to_string app_ptr;;
9595-- : 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"}|};;
9595+- : 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"}|};;
9696val obj : Jsont.json = {"foo":"bar"}
9797# add (of_string "/baz") obj ~value:(Jsont.Json.string "qux");;
9898-- : 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"]}|};;
9898+- : 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"]}|};;
9999val arr_obj : Jsont.json = {"foo":["a","b"]}
100100# add (of_string "/foo/1") arr_obj ~value:(Jsont.Json.string "X");;
101101- : 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");;
102102-- : 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");;
103103-- : 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"]}|};;
102102+- : 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");;
103103+- : 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"]}|};;
104104val items : Jsont.json = {"items":["x"]}
105105# add (of_string "/items/0") items ~value:(Jsont.Json.string "y");;
106106- : Jsont.json = {"items":["y","x"]}
107107# add (of_string "/items/-") items ~value:(Jsont.Json.string "z");;
108108-- : 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"}|};;
108108+- : 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"}|};;
109109val two_fields : Jsont.json = {"foo":"bar","baz":"qux"}
110110# remove (of_string_nav "/baz") two_fields ;;
111111- : 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"]}|};;
112112val three_elem : Jsont.json = {"foo":["a","b","c"]}
113113# remove (of_string_nav "/foo/1") three_elem ;;
114114-- : 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")
114114+- : 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")
115115 ;;
116116-- : 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":{}}|};;
116116+- : 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":{}}|};;
117117val nested : Jsont.json = {"foo":{"bar":"baz"},"qux":{}}
118118# move ~from:(of_string_nav "/foo/bar") ~path:(of_string "/qux/thud") nested;;
119119-- : 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"}}|};;
119119+- : 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"}}|};;
120120val to_copy : Jsont.json = {"foo":{"bar":"baz"}}
121121# copy ~from:(of_string_nav "/foo/bar") ~path:(of_string "/foo/qux") to_copy;;
122122-- : 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");;
122122+- : 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");;
123123- : bool = true
124124# test (of_string_nav "/foo") obj ~expected:(Jsont.Json.string "wrong");;
125125- : 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"];;
···127127# to_string p;;
128128- : string = "/a~1b"
129129# of_string_nav "/a~1b";;
130130-- : 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";;
130130+- : 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";;
131131- : string = "hello"
132132# Token.escape "a/b";;
133133- : string = "a~1b"
···148148- : 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"];;
149149val port_ptr : nav t = [Mem "database"; Mem "port"]
150150# to_string port_ptr;;
151151-- : 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];;
151151+- : 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];;
152152val first_feature_ptr : nav t = [Mem "features"; Nth 0]
153153# to_string first_feature_ptr;;
154154-- : 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";;
154154+- : 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";;
155155val db_ptr : nav t = [Mem "database"]
156156# let creds_ptr = db_ptr / mem "credentials";;
157157val creds_ptr : nav t = [Mem "database"; Mem "credentials"]
···172172 "features": ["auth", "logging", "metrics"]
173173 }|};;
174174val config_json : Jsont.json =
175175- {"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";;
175175+ {"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";;
176176val nav : nav t = [Mem "database"; Mem "host"]
177177# let db_host =
178178 Jsont.Json.decode
···221221 (path (of_string_nav "/database/port") Jsont.int)
222222 config_json
223223 |> Result.get_ok;;
224224-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"]}|};;
224224+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"]}|};;
225225val tasks : Jsont.json = {"tasks":["buy milk"]}
226226# set (of_string "/tasks/0") tasks ~value:(Jsont.Json.string "buy eggs");;
227227- : Jsont.json = {"tasks":["buy eggs"]}
228228# set (of_string "/tasks/-") tasks ~value:(Jsont.Json.string "call mom");;
229229-- : 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";;
229229+- : 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";;
230230- : [ `Append of append t | `Nav of nav t ] = `Nav [Mem "tasks"; Nth 0]
231231# of_string_kind "/tasks/-";;
232232- : [ `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
···205205{"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}
206206]}
207207208208-The empty pointer ({!root}) returns the whole document.
208208+The empty pointer ({!Json_pointer.root}) returns the whole document.
209209210210{2 Object Member Access}
211211···376376type any (* Existential: wraps either nav or append *)
377377v}
378378379379-When you parse a pointer with {!of_string}, you get an {!any} pointer
379379+When you parse a pointer with {!Json_pointer.of_string}, you get an {!type:Json_pointer.any} pointer
380380that can be used directly with mutation operations:
381381382382{@ocaml[
···386386- : any = Any <abstr>
387387]}
388388389389-The [-] creates an append pointer. The {!any} type wraps either kind,
390390-making it ergonomic to use with operations like {!set} and {!add}.
389389+The [-] creates an append pointer. The {!type:Json_pointer.any} type wraps either kind,
390390+making it ergonomic to use with operations like {!Json_pointer.set} and {!Json_pointer.add}.
391391392392{2 Why Two Pointer Types?}
393393···400400So you {b cannot use [get] or [find]} with an append pointer - it makes
401401no sense to retrieve a value from a position that doesn't exist! The
402402library enforces this:
403403-- Use {!of_string_nav} when you need to call {!get} or {!find}
404404-- Use {!of_string} (returns {!any}) for mutation operations
403403+- Use {!Json_pointer.of_string_nav} when you need to call {!Json_pointer.get} or {!Json_pointer.find}
404404+- Use {!Json_pointer.of_string} (returns {!type:Json_pointer.any}) for mutation operations
405405406406-Mutation operations like {!add} accept {!any} directly:
406406+Mutation operations like {!Json_pointer.add} accept {!type:Json_pointer.any} directly:
407407408408{x@ocaml[
409409# let arr_obj = parse_json {|{"foo":["a","b"]}|};;
···412412- : Jsont.json = {"foo":["a","b","c"]}
413413]x}
414414415415-For retrieval operations, use {!of_string_nav} which ensures the pointer
415415+For retrieval operations, use {!Json_pointer.of_string_nav} which ensures the pointer
416416doesn't contain [-]:
417417418418{@ocaml[
···425425426426{2 Creating Append Pointers Programmatically}
427427428428-You can convert a navigation pointer to an append pointer using {!at_end}:
428428+You can convert a navigation pointer to an append pointer using {!Json_pointer.at_end}:
429429430430{@ocaml[
431431# let nav_ptr = of_string_nav "/foo";;
···444444445445{2 Add}
446446447447-The {!add} operation inserts a value at a location. It accepts {!any}
448448-pointers, so you can use {!of_string} directly:
447447+The {!Json_pointer.add} operation inserts a value at a location. It accepts {!type:Json_pointer.any}
448448+pointers, so you can use {!Json_pointer.of_string} directly:
449449450450{x@ocaml[
451451# let obj = parse_json {|{"foo":"bar"}|};;
···454454- : Jsont.json = {"foo":"bar","baz":"qux"}
455455]x}
456456457457-For arrays, {!add} inserts BEFORE the specified index:
457457+For arrays, {!Json_pointer.add} inserts BEFORE the specified index:
458458459459{x@ocaml[
460460# let arr_obj = parse_json {|{"foo":["a","b"]}|};;
···470470- : Jsont.json = {"foo":["a","b","c"]}
471471]x}
472472473473-You can also use {!at_end} to create an append pointer programmatically:
473473+You can also use {!Json_pointer.at_end} to create an append pointer programmatically:
474474475475{x@ocaml[
476476# add (any (at_end (of_string_nav "/foo"))) arr_obj ~value:(Jsont.Json.string "c");;
···479479480480{2 Ergonomic Mutation with [any]}
481481482482-Since {!add}, {!set}, {!move}, and {!copy} accept {!any} pointers, you can
483483-use {!of_string} directly without any pattern matching. This makes JSON
482482+Since {!Json_pointer.add}, {!Json_pointer.set}, {!Json_pointer.move}, and {!Json_pointer.copy} accept {!type:Json_pointer.any} pointers, you can
483483+use {!Json_pointer.of_string} directly without any pattern matching. This makes JSON
484484Patch implementations straightforward:
485485486486{x@ocaml[
···497497498498{2 Remove}
499499500500-The {!remove} operation deletes a value. It only accepts [nav t] because
500500+The {!Json_pointer.remove} operation deletes a value. It only accepts [nav t] because
501501you can only remove something that exists:
502502503503{x@ocaml[
···518518519519{2 Replace}
520520521521-The {!replace} operation updates an existing value:
521521+The {!Json_pointer.replace} operation updates an existing value:
522522523523{@ocaml[
524524# replace (of_string_nav "/foo") obj ~value:(Jsont.Json.string "baz")
···526526- : Jsont.json = {"foo":"baz"}
527527]}
528528529529-Unlike {!add}, {!replace} requires the target to already exist (hence [nav t]).
529529+Unlike {!Json_pointer.add}, {!Json_pointer.replace} requires the target to already exist (hence [nav t]).
530530Attempting to replace a nonexistent path raises an error.
531531532532{2 Move}
533533534534-The {!move} operation relocates a value. The source ([from]) must be a [nav t]
534534+The {!Json_pointer.move} operation relocates a value. The source ([from]) must be a [nav t]
535535(you can only move something that exists), but the destination ([path])
536536-accepts {!any}:
536536+accepts {!type:Json_pointer.any}:
537537538538{x@ocaml[
539539# let nested = parse_json {|{"foo":{"bar":"baz"},"qux":{}}|};;
···544544545545{2 Copy}
546546547547-The {!copy} operation duplicates a value (same typing as {!move}):
547547+The {!Json_pointer.copy} operation duplicates a value (same typing as {!Json_pointer.move}):
548548549549{x@ocaml[
550550# let to_copy = parse_json {|{"foo":{"bar":"baz"}}|};;
···555555556556{2 Test}
557557558558-The {!test} operation verifies a value (useful in JSON Patch):
558558+The {!Json_pointer.test} operation verifies a value (useful in JSON Patch):
559559560560{@ocaml[
561561# test (of_string_nav "/foo") obj ~expected:(Jsont.Json.string "bar");;
···597597598598{2 Escaping in Action}
599599600600-The {!Token} module exposes the escaping functions:
600600+The {!Json_pointer.Token} module exposes the escaping functions:
601601602602{@ocaml[
603603# Token.escape "hello";;
···692692- : string = "/database/port"
693693]}
694694695695-For array access, use the {!nth} helper:
695695+For array access, use the {!Json_pointer.nth} helper:
696696697697{@ocaml[
698698# let first_feature_ptr = make [mem "features"; nth 0];;
···703703704704{2 Pointer Navigation}
705705706706-You can build pointers incrementally using the [/] operator (or {!append_index}):
706706+You can build pointers incrementally using the [/] operator (or {!Json_pointer.append_index}):
707707708708{@ocaml[
709709# let db_ptr = of_string_nav "/database";;
···749749750750{2 Typed Access with [path]}
751751752752-The {!path} combinator combines pointer navigation with typed decoding:
752752+The {!Json_pointer.path} combinator combines pointer navigation with typed decoding:
753753754754{@ocaml[
755755# let nav = of_string_nav "/database/host";;
···844844845845{2 Updates with Polymorphic Pointers}
846846847847-The {!set} and {!add} functions accept {!any} pointers, which means you can
848848-use the result of {!of_string} directly without pattern matching:
847847+The {!Json_pointer.set} and {!Json_pointer.add} functions accept {!type:Json_pointer.any} pointers, which means you can
848848+use the result of {!Json_pointer.of_string} directly without pattern matching:
849849850850{x@ocaml[
851851# let tasks = parse_json {|{"tasks":["buy milk"]}|};;
···859859This is useful for implementing JSON Patch ({{:https://datatracker.ietf.org/doc/html/rfc6902}RFC 6902}) where
860860operations like ["add"] can target either existing positions or the
861861append marker. If you need to distinguish between pointer types at runtime,
862862-use {!of_string_kind} which returns a polymorphic variant:
862862+use {!Json_pointer.of_string_kind} which returns a polymorphic variant:
863863864864{x@ocaml[
865865# of_string_kind "/tasks/0";;
+26-26
src/json_pointer.mli
···7777 (** [unescape s] unescapes a JSON Pointer reference token.
7878 Specifically, [~1] becomes [/] and [~0] becomes [~].
79798080- @raise Jsont.Error if [s] contains invalid escape sequences
8080+ @raise Jsont.Error.Error if [s] contains invalid escape sequences
8181 (a [~] not followed by [0] or [1]). *)
8282end
8383···137137138138(** {2 Existential wrapper}
139139140140- The {!any} type wraps a pointer of unknown phantom type, allowing
140140+ The {!type: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···188188(** {2:coercion Coercion and inspection} *)
189189190190val 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}
191191+(** [any p] wraps a typed pointer in the existential {!type:any} type.
192192+ Use this when you have a [nav t] or [append t] but need an {!type:any}
193193 for use with functions like {!set} or {!add}. *)
194194195195val is_nav : any -> bool
···202202203203val 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. *)
205205+ @raise Jsont.Error.Error if [p] is an append pointer. *)
206206207207(** {2:parsing Parsing} *)
208208209209val of_string : string -> any
210210(** [of_string s] parses a JSON Pointer from its string representation.
211211212212- Returns an {!any} pointer that can be used directly with mutation
212212+ Returns an {!type: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.
215215···217217 with [/]. Each segment between [/] characters is unescaped as a
218218 reference token.
219219220220- @raise Jsont.Error if [s] has invalid syntax:
220220+ @raise Jsont.Error.Error if [s] has invalid syntax:
221221 - Non-empty string not starting with [/]
222222 - Invalid escape sequence ([~] not followed by [0] or [1])
223223 - [-] appears in non-final position *)
···230230 differently, or when you need a typed pointer for operations that
231231 require a specific kind.
232232233233- @raise Jsont.Error if [s] has invalid syntax. *)
233233+ @raise Jsont.Error.Error if [s] has invalid syntax. *)
234234235235val of_string_nav : string -> nav t
236236(** [of_string_nav s] parses a JSON Pointer that must not contain [-].
···238238 Use this when you need a {!nav} pointer for retrieval operations
239239 like {!get} or {!find}.
240240241241- @raise Jsont.Error if [s] has invalid syntax or contains [-]. *)
241241+ @raise Jsont.Error.Error if [s] has invalid syntax or contains [-]. *)
242242243243val of_string_result : string -> (any, string) result
244244(** [of_string_result s] is like {!of_string} but returns a result
···251251 according to {{:https://www.rfc-editor.org/rfc/rfc3986}RFC 3986}.
252252 The leading [#] should {b not} be included in [s].
253253254254- @raise Jsont.Error on invalid syntax or invalid percent-encoding. *)
254254+ @raise Jsont.Error.Error on invalid syntax or invalid percent-encoding. *)
255255256256val of_uri_fragment_nav : string -> nav t
257257(** [of_uri_fragment_nav s] is like {!of_uri_fragment} but requires
258258 the pointer to not contain [-].
259259260260- @raise Jsont.Error if invalid or contains [-]. *)
260260+ @raise Jsont.Error.Error if invalid or contains [-]. *)
261261262262val of_uri_fragment_result : string -> (any, string) result
263263(** [of_uri_fragment_result s] is like {!of_uri_fragment} but returns
···310310311311(** {1 Evaluation}
312312313313- These functions evaluate a JSON Pointer against a {!Jsont.json} value
313313+ These functions evaluate a JSON Pointer against a {!type:Jsont.json} value
314314 to retrieve the referenced value. They only accept {!nav} pointers
315315 since {!append} pointers refer to nonexistent positions. *)
316316317317val get : nav t -> Jsont.json -> Jsont.json
318318(** [get p json] retrieves the value at pointer [p] in [json].
319319320320- @raise Jsont.Error if:
320320+ @raise Jsont.Error.Error if:
321321 - The pointer references a nonexistent object member
322322 - The pointer references an out-of-bounds array index
323323 - An index type doesn't match the JSON value (e.g., [Nth]
···332332333333(** {1 Mutation}
334334335335- These functions modify a {!Jsont.json} value at a location specified
335335+ These functions modify a {!type:Jsont.json} value at a location specified
336336 by a JSON Pointer. They are designed to support
337337 {{:https://www.rfc-editor.org/rfc/rfc6902}RFC 6902 JSON Patch}
338338 operations.
···341341 applied; they do not mutate the input.
342342343343 Functions that support the [-] token ({!set}, {!add}, {!move}, {!copy})
344344- accept {!any} pointers, making them easy to use with {!of_string}.
344344+ accept {!type:any} pointers, making them easy to use with {!of_string}.
345345 Functions that require an existing element ({!remove}, {!replace})
346346 only accept {!nav} pointers. *)
347347···350350351351 For {!append} pointers, appends [value] to the end of the array.
352352353353- This accepts {!any} pointers directly from {!of_string}:
353353+ This accepts {!type:any} pointers directly from {!of_string}:
354354 {[set (of_string "/tasks/-") json ~value:(Jsont.Json.string "new task")]}
355355356356- @raise Jsont.Error if the pointer doesn't resolve to an existing
356356+ @raise Jsont.Error.Error if the pointer doesn't resolve to an existing
357357 location (except for {!append} pointers on arrays). *)
358358359359val add : any -> Jsont.json -> value:Jsont.json -> Jsont.json
···368368 valid (0 to length inclusive).}
369369 {- For {!append} pointers: Appends [value] to the array.}}
370370371371- @raise Jsont.Error if:
371371+ @raise Jsont.Error.Error if:
372372 - The parent of the target location doesn't exist
373373 - An array index is out of bounds (except for {!append} pointers)
374374 - The parent is not an object or array *)
···379379 For objects, removes the member. For arrays, removes the element
380380 and shifts subsequent elements.
381381382382- @raise Jsont.Error if:
382382+ @raise Jsont.Error.Error if:
383383 - [p] is the root (cannot remove the root)
384384 - The pointer doesn't resolve to an existing value *)
385385···388388389389 Unlike {!add}, this requires the target to exist.
390390391391- @raise Jsont.Error if the pointer doesn't resolve to an existing value. *)
391391+ @raise Jsont.Error.Error if the pointer doesn't resolve to an existing value. *)
392392393393val move : from:nav t -> path:any -> Jsont.json -> Jsont.json
394394(** [move ~from ~path json] moves the value from [from] to [path].
···396396 This is equivalent to {!remove} at [from] followed by {!add}
397397 at [path] with the removed value.
398398399399- @raise Jsont.Error if:
399399+ @raise Jsont.Error.Error if:
400400 - [from] doesn't resolve to a value
401401 - [path] is a proper prefix of [from] (would create a cycle) *)
402402···406406 This is equivalent to {!get} at [from] followed by {!add}
407407 at [path] with the retrieved value.
408408409409- @raise Jsont.Error if [from] doesn't resolve to a value. *)
409409+ @raise Jsont.Error.Error if [from] doesn't resolve to a value. *)
410410411411val test : nav t -> Jsont.json -> expected:Jsont.json -> bool
412412(** [test p json ~expected] tests if the value at [p] equals [expected].
···511511 The syntax is the same as RFC 6901 JSON Pointer, except [*] is allowed
512512 as a reference token for array mapping.
513513514514- @raise Jsont.Error if [s] has invalid syntax. *)
514514+ @raise Jsont.Error.Error if [s] has invalid syntax. *)
515515516516 val of_string_result : string -> (t, string) result
517517 (** [of_string_result s] is like {!of_string} but returns a result. *)
···528528 For [*] tokens on arrays, maps through all elements and collects results.
529529 Results that are arrays are flattened into the output.
530530531531- @raise Jsont.Error if:
531531+ @raise Jsont.Error.Error if:
532532 - A standard token doesn't resolve (member not found, index out of bounds)
533533 - [*] is used on a non-array value
534534 - [-] appears in the pointer (not supported in JMAP extended pointers) *)
···561561 (Jsont.list Jsont.string)
562562 ]}
563563564564- @raise Jsont.Error if the pointer fails to resolve (and no [absent])
564564+ @raise Jsont.Error.Error if the pointer fails to resolve (and no [absent])
565565 or if decoding with [codec] fails. *)
566566567567 val path_list : t -> 'a Jsont.t -> 'a list Jsont.t
···576576 Jmap.path (Jmap.of_string "/list/*/id") (Jsont.list Jsont.string)
577577 ]}
578578579579- @raise Jsont.Error if pointer resolution fails, the result is not an array,
579579+ @raise Jsont.Error.Error if pointer resolution fails, the result is not an array,
580580 or any element fails to decode. *)
581581end
+4-4
src/top/json_pointer_top.mli
···11-(** Toplevel printers for {!Json_pointer}, {!Jsont.json}, and {!Jsont.Error.t}.
11+(** Toplevel printers for {!Json_pointer}, {!type:Jsont.json}, and {!Jsont.Error.t}.
2233 Printers are automatically installed when the library is loaded:
44 {[
···1212 ]}
13131414 JSON values will display as formatted JSON strings:
1515- {[
1515+ {v
1616 # Jsont_bytesrw.decode_string Jsont.json {|{"foo": [1, 2]}|};;
1717 - : Jsont.json = {"foo": [1, 2]}
1818- ]}
1818+ v}
19192020 And errors will display as readable messages:
2121 {[
···3232 Suitable for use with [#install_printer]. *)
33333434val json_printer : Format.formatter -> Jsont.json -> unit
3535-(** [json_printer] formats a {!Jsont.json} value as a human-readable
3535+(** [json_printer] formats a {!type:Jsont.json} value as a human-readable
3636 JSON string. Suitable for use with [#install_printer]. *)
37373838val error_printer : Format.formatter -> Jsont.Error.t -> unit