RFC6901 JSON Pointer implementation in OCaml using jsont

Fix build and doc warnings, remove dune subtree

Build fixes:
- Change odoc extension library refs to odoc.extension_api
- Add missing assets field to extension result records

Documentation fixes:
- Fix cross-package references using documentation dependencies
- Fix reference syntax: {!Stdlib.String.escaped}, {!Webfinger.Jrd}, etc.
- Fix heading levels and anchor syntax (#→.)
- Add @canonical annotation for Loc0.t
- Escape braces in doc comments
- Add asset support via (files ...) in documentation stanza

Infrastructure:
- Remove dune subtree (now pinned to jonludlam/dune#odoc-assets-support)
- Add dune-workspace with fatal odoc warnings
- Remove conflicting odoc-config.sexp install stanzas

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+105 -105
+44 -44
doc/index.html
··· 24 24 } 25 25 .x-ocaml-wrapper { max-width: 600px; } 26 26 } 27 - </style></head><body class="odoc"><nav class="odoc-nav"><a href="index.html">Up</a> – <a href="index.html">Index</a> » tutorial</nav><header class="odoc-preamble"><h1 id="json-pointer-tutorial"><a href="#json-pointer-tutorial" class="anchor"></a>JSON Pointer Tutorial</h1><p>This tutorial introduces JSON Pointer as defined in <a href="https://www.rfc-editor.org/rfc/rfc6901">RFC 6901</a>, and demonstrates the <code>json-pointer</code> OCaml library through interactive examples.</p></header><div class="odoc-tocs"><nav class="odoc-toc odoc-local-toc"><ul><li><a href="#json-pointer-vs-json-path">JSON Pointer vs JSON Path</a></li><li><a href="#setup">Setup</a></li><li><a href="#what-is-json-pointer?">What is JSON Pointer?</a></li><li><a href="#syntax:-reference-tokens">Syntax: Reference Tokens</a><ul><li><a href="#the-index-type">The Index Type</a></li><li><a href="#invalid-syntax">Invalid Syntax</a></li></ul></li><li><a href="#evaluation:-navigating-json">Evaluation: Navigating JSON</a><ul><li><a href="#the-root-pointer">The Root Pointer</a></li><li><a href="#object-member-access">Object Member Access</a></li><li><a href="#array-index-access">Array Index Access</a></li><li><a href="#empty-string-as-key">Empty String as Key</a></li><li><a href="#keys-with-special-characters">Keys with Special Characters</a></li><li><a href="#other-special-characters-(no-escaping-needed)">Other Special Characters (No Escaping Needed)</a></li><li><a href="#error-conditions">Error Conditions</a></li><li><a href="#array-index-rules">Array Index Rules</a></li></ul></li><li><a href="#the-end-of-array-marker:---and-type-safety">The End-of-Array Marker: <code>-</code> and Type Safety</a><ul><li><a href="#navigation-vs-append-pointers">Navigation vs Append Pointers</a></li><li><a href="#why-two-pointer-types?">Why Two Pointer Types?</a></li><li><a href="#creating-append-pointers-programmatically">Creating Append Pointers Programmatically</a></li></ul></li><li><a href="#mutation-operations">Mutation Operations</a><ul><li><a href="#add">Add</a></li><li><a href="#ergonomic-mutation-with-any">Ergonomic Mutation with <code>any</code></a></li><li><a href="#remove">Remove</a></li><li><a href="#replace">Replace</a></li><li><a href="#move">Move</a></li><li><a href="#copy">Copy</a></li><li><a href="#test">Test</a></li></ul></li><li><a href="#escaping">Escaping Special Characters</a><ul><li><a href="#the-library-handles-escaping-automatically">The Library Handles Escaping Automatically</a></li><li><a href="#escaping-in-action">Escaping in Action</a></li><li><a href="#unescaping">Unescaping</a></li><li><a href="#the-order-matters!">The Order Matters!</a></li></ul></li><li><a href="#uri-fragment-encoding">URI Fragment Encoding</a></li><li><a href="#building-pointers-programmatically">Building Pointers Programmatically</a><ul><li><a href="#pointer-navigation">Pointer Navigation</a></li></ul></li><li><a href="#jsont-integration">Jsont Integration</a><ul><li><a href="#typed-access-with-path">Typed Access with <code>path</code></a></li><li><a href="#default-values-with-~absent">Default Values with <code>~absent</code></a></li><li><a href="#nested-path-extraction">Nested Path Extraction</a></li><li><a href="#comparison:-raw-vs-typed-access">Comparison: Raw vs Typed Access</a></li><li><a href="#updates-with-polymorphic-pointers">Updates with Polymorphic Pointers</a></li></ul></li><li><a href="#summary">Summary</a><ul><li><a href="#key-points-on-json-pointer-vs-json-path">Key Points on JSON Pointer vs JSON Path</a></li></ul></li></ul></nav></div><div class="odoc-content"><h2 id="json-pointer-vs-json-path"><a href="#json-pointer-vs-json-path" class="anchor"></a>JSON Pointer vs JSON Path</h2><p>Before diving in, it's worth understanding the difference between JSON Pointer and JSON Path, as they serve different purposes:</p><p><b>JSON Pointer</b> (<a href="https://datatracker.ietf.org/doc/html/rfc6901">RFC 6901</a>) is an <em>indicator syntax</em> that specifies a <em>single location</em> within JSON data. It always identifies at most one value.</p><p><b>JSON Path</b> is a <em>query syntax</em> that can <em>search</em> JSON data and return <em>multiple</em> values matching specified criteria.</p><p>Use JSON Pointer when you need to address a single, specific location (like JSON Schema's <code>$ref</code>). Use JSON Path when you might need multiple results (like Kubernetes queries).</p><p>The <code>json-pointer</code> library implements JSON Pointer and integrates with the <code>Jsont.Path</code> type for representing navigation indices.</p><h2 id="setup"><a href="#setup" class="anchor"></a>Setup</h2><p>First, let's set up our environment. In the toplevel, you can load the library with <code>#require "json-pointer.top";;</code> which will automatically install pretty printers.</p><div class="x-ocaml-wrapper"><x-ocaml>Json_pointer_top.install ();; 27 + </style></head><body class="odoc"><nav class="odoc-nav"><a href="index.html">Up</a> – <a href="index.html">Index</a> » tutorial</nav><header class="odoc-preamble"><h1 id="json-pointer-tutorial"><a href="#json-pointer-tutorial" class="anchor"></a>JSON Pointer Tutorial</h1><p>This tutorial introduces JSON Pointer as defined in <a href="https://www.rfc-editor.org/rfc/rfc6901">RFC 6901</a>, and demonstrates the <code>json-pointer</code> OCaml library through interactive examples.</p></header><div class="odoc-tocs"><nav class="odoc-toc odoc-local-toc"><ul><li><a href="#json-pointer-vs-json-path">JSON Pointer vs JSON Path</a></li><li><a href="#setup">Setup</a></li><li><a href="#what-is-json-pointer?">What is JSON Pointer?</a></li><li><a href="#syntax:-reference-tokens">Syntax: Reference Tokens</a><ul><li><a href="#the-index-type">The Index Type</a></li><li><a href="#invalid-syntax">Invalid Syntax</a></li></ul></li><li><a href="#evaluation:-navigating-json">Evaluation: Navigating JSON</a><ul><li><a href="#the-root-pointer">The Root Pointer</a></li><li><a href="#object-member-access">Object Member Access</a></li><li><a href="#array-index-access">Array Index Access</a></li><li><a href="#empty-string-as-key">Empty String as Key</a></li><li><a href="#keys-with-special-characters">Keys with Special Characters</a></li><li><a href="#other-special-characters-(no-escaping-needed)">Other Special Characters (No Escaping Needed)</a></li><li><a href="#error-conditions">Error Conditions</a></li><li><a href="#array-index-rules">Array Index Rules</a></li></ul></li><li><a href="#the-end-of-array-marker:---and-type-safety">The End-of-Array Marker: <code>-</code> and Type Safety</a><ul><li><a href="#navigation-vs-append-pointers">Navigation vs Append Pointers</a></li><li><a href="#why-two-pointer-types?">Why Two Pointer Types?</a></li><li><a href="#creating-append-pointers-programmatically">Creating Append Pointers Programmatically</a></li></ul></li><li><a href="#mutation-operations">Mutation Operations</a><ul><li><a href="#add">Add</a></li><li><a href="#ergonomic-mutation-with-any">Ergonomic Mutation with <code>any</code></a></li><li><a href="#remove">Remove</a></li><li><a href="#replace">Replace</a></li><li><a href="#move">Move</a></li><li><a href="#copy">Copy</a></li><li><a href="#test">Test</a></li></ul></li><li><a href="#escaping">Escaping Special Characters</a><ul><li><a href="#the-library-handles-escaping-automatically">The Library Handles Escaping Automatically</a></li><li><a href="#escaping-in-action">Escaping in Action</a></li><li><a href="#unescaping">Unescaping</a></li><li><a href="#the-order-matters!">The Order Matters!</a></li></ul></li><li><a href="#uri-fragment-encoding">URI Fragment Encoding</a></li><li><a href="#building-pointers-programmatically">Building Pointers Programmatically</a><ul><li><a href="#pointer-navigation">Pointer Navigation</a></li></ul></li><li><a href="#jsont-integration">Jsont Integration</a><ul><li><a href="#typed-access-with-path">Typed Access with <code>path</code></a></li><li><a href="#default-values-with-~absent">Default Values with <code>~absent</code></a></li><li><a href="#nested-path-extraction">Nested Path Extraction</a></li><li><a href="#comparison:-raw-vs-typed-access">Comparison: Raw vs Typed Access</a></li><li><a href="#updates-with-polymorphic-pointers">Updates with Polymorphic Pointers</a></li></ul></li><li><a href="#summary">Summary</a><ul><li><a href="#key-points-on-json-pointer-vs-json-path">Key Points on JSON Pointer vs JSON Path</a></li></ul></li></ul></nav></div><div class="odoc-content"><h2 id="json-pointer-vs-json-path"><a href="#json-pointer-vs-json-path" class="anchor"></a>JSON Pointer vs JSON Path</h2><p>Before diving in, it's worth understanding the difference between JSON Pointer and JSON Path, as they serve different purposes:</p><p><b>JSON Pointer</b> (<a href="https://datatracker.ietf.org/doc/html/rfc6901">RFC 6901</a>) is an <em>indicator syntax</em> that specifies a <em>single location</em> within JSON data. It always identifies at most one value.</p><p><b>JSON Path</b> is a <em>query syntax</em> that can <em>search</em> JSON data and return <em>multiple</em> values matching specified criteria.</p><p>Use JSON Pointer when you need to address a single, specific location (like JSON Schema's <code>$ref</code>). Use JSON Path when you might need multiple results (like Kubernetes queries).</p><p>The <code>json-pointer</code> library implements JSON Pointer and integrates with the <code>Jsont.Path</code> type for representing navigation indices.</p><h2 id="setup"><a href="#setup" class="anchor"></a>Setup</h2><p>First, let's set up our environment. In the toplevel, you can load the library with <code>#require "json-pointer.top";;</code> which will automatically install pretty printers.</p><div><div class="x-ocaml-wrapper"><x-ocaml>Json_pointer_top.install ();; 28 28 open Json_pointer;; 29 29 let parse_json s = 30 30 match Jsont_bytesrw.decode_string Jsont.json s with 31 31 | Ok json -&gt; json 32 - | Error e -&gt; failwith e;;</x-ocaml></div><h2 id="what-is-json-pointer?"><a href="#what-is-json-pointer?" class="anchor"></a>What is JSON Pointer?</h2><p>From <a href="https://datatracker.ietf.org/doc/html/rfc6901#section-1">RFC 6901, Section 1</a>:</p><p><i>JSON Pointer defines a string syntax for identifying a specific value within a JavaScript Object Notation (JSON) document.</i></p><p>In other words, JSON Pointer is an addressing scheme for locating values inside a JSON structure. Think of it like a filesystem path, but for JSON documents instead of files.</p><p>For example, given this JSON document:</p><div class="x-ocaml-wrapper"><x-ocaml>let users_json = parse_json {|{ 32 + | Error e -&gt; failwith e;;</x-ocaml></div></div><h2 id="what-is-json-pointer?"><a href="#what-is-json-pointer?" class="anchor"></a>What is JSON Pointer?</h2><p>From <a href="https://datatracker.ietf.org/doc/html/rfc6901#section-1">RFC 6901, Section 1</a>:</p><p><i>JSON Pointer defines a string syntax for identifying a specific value within a JavaScript Object Notation (JSON) document.</i></p><p>In other words, JSON Pointer is an addressing scheme for locating values inside a JSON structure. Think of it like a filesystem path, but for JSON documents instead of files.</p><p>For example, given this JSON document:</p><div><div class="x-ocaml-wrapper"><x-ocaml>let users_json = parse_json {|{ 33 33 "users": [ 34 34 {"name": "Alice", "age": 30}, 35 35 {"name": "Bob", "age": 25} 36 36 ] 37 - }|};;</x-ocaml></div><p>The JSON Pointer <code>/users/0/name</code> refers to the string <code>"Alice"</code>:</p><div class="x-ocaml-wrapper"><x-ocaml>let ptr = of_string_nav "/users/0/name";; 38 - get ptr users_json;;</x-ocaml></div><p>In OCaml, this is represented by the <code>'a Json_pointer.t</code> type - a sequence of navigation steps from the document root to a target value. The phantom type parameter <code>'a</code> encodes whether this is a navigation pointer or an append pointer (more on this later).</p><h2 id="syntax:-reference-tokens"><a href="#syntax:-reference-tokens" class="anchor"></a>Syntax: Reference Tokens</h2><p><a href="https://datatracker.ietf.org/doc/html/rfc6901#section-3">RFC 6901, Section 3</a> defines the syntax:</p><p><i>A JSON Pointer is a Unicode string containing a sequence of zero or more reference tokens, each prefixed by a '/' (%x2F) character.</i></p><p>The grammar is elegantly simple:</p><pre>json-pointer = *( "/" reference-token ) 39 - reference-token = *( unescaped / escaped )</pre><p>This means:</p><ul><li>The empty string <code>""</code> is a valid pointer (it refers to the whole document)</li><li>Every non-empty pointer starts with <code>/</code></li><li>Everything between <code>/</code> characters is a "reference token"</li></ul><p>Let's see this in action:</p><div class="x-ocaml-wrapper"><x-ocaml>of_string_nav "";;</x-ocaml></div><p>The empty pointer has no reference tokens - it points to the root.</p><div class="x-ocaml-wrapper"><x-ocaml>of_string_nav "/foo";;</x-ocaml></div><p>The pointer <code>/foo</code> has one token: <code>foo</code>. Since it's not a number, it's interpreted as an object member name (<code>Mem</code>).</p><div class="x-ocaml-wrapper"><x-ocaml>of_string_nav "/foo/0";;</x-ocaml></div><p>Here we have two tokens: <code>foo</code> (a member name) and <code>0</code> (interpreted as an array index <code>Nth</code>).</p><div class="x-ocaml-wrapper"><x-ocaml>of_string_nav "/foo/bar/baz";;</x-ocaml></div><p>Multiple tokens navigate deeper into nested structures.</p><h3 id="the-index-type"><a href="#the-index-type" class="anchor"></a>The Index Type</h3><p>Each reference token is represented using <code>Jsont.Path.index</code>:</p><pre>type index = Jsont.Path.index 37 + }|};;</x-ocaml></div></div><p>The JSON Pointer <code>/users/0/name</code> refers to the string <code>"Alice"</code>:</p><div><div class="x-ocaml-wrapper"><x-ocaml>let ptr = of_string_nav "/users/0/name";; 38 + get ptr users_json;;</x-ocaml></div></div><p>In OCaml, this is represented by the <code>'a Json_pointer.t</code> type - a sequence of navigation steps from the document root to a target value. The phantom type parameter <code>'a</code> encodes whether this is a navigation pointer or an append pointer (more on this later).</p><h2 id="syntax:-reference-tokens"><a href="#syntax:-reference-tokens" class="anchor"></a>Syntax: Reference Tokens</h2><p><a href="https://datatracker.ietf.org/doc/html/rfc6901#section-3">RFC 6901, Section 3</a> defines the syntax:</p><p><i>A JSON Pointer is a Unicode string containing a sequence of zero or more reference tokens, each prefixed by a '/' (%x2F) character.</i></p><p>The grammar is elegantly simple:</p><pre>json-pointer = *( "/" reference-token ) 39 + reference-token = *( unescaped / escaped )</pre><p>This means:</p><ul><li>The empty string <code>""</code> is a valid pointer (it refers to the whole document)</li><li>Every non-empty pointer starts with <code>/</code></li><li>Everything between <code>/</code> characters is a "reference token"</li></ul><p>Let's see this in action:</p><div><div class="x-ocaml-wrapper"><x-ocaml>of_string_nav "";;</x-ocaml></div></div><p>The empty pointer has no reference tokens - it points to the root.</p><div><div class="x-ocaml-wrapper"><x-ocaml>of_string_nav "/foo";;</x-ocaml></div></div><p>The pointer <code>/foo</code> has one token: <code>foo</code>. Since it's not a number, it's interpreted as an object member name (<code>Mem</code>).</p><div><div class="x-ocaml-wrapper"><x-ocaml>of_string_nav "/foo/0";;</x-ocaml></div></div><p>Here we have two tokens: <code>foo</code> (a member name) and <code>0</code> (interpreted as an array index <code>Nth</code>).</p><div><div class="x-ocaml-wrapper"><x-ocaml>of_string_nav "/foo/bar/baz";;</x-ocaml></div></div><p>Multiple tokens navigate deeper into nested structures.</p><h3 id="the-index-type"><a href="#the-index-type" class="anchor"></a>The Index Type</h3><p>Each reference token is represented using <code>Jsont.Path.index</code>:</p><pre>type index = Jsont.Path.index 40 40 (* = Jsont.Path.Mem of string * Jsont.Meta.t 41 - | Jsont.Path.Nth of int * Jsont.Meta.t *)</pre><p>The <code>Mem</code> constructor is for object member access, and <code>Nth</code> is for array index access. The member name is <b>unescaped</b> - you work with the actual key string (like <code>"a/b"</code>) and the library handles any escaping needed for the JSON Pointer string representation.</p><h3 id="invalid-syntax"><a href="#invalid-syntax" class="anchor"></a>Invalid Syntax</h3><p>What happens if a pointer doesn't start with <code>/</code>?</p><div class="x-ocaml-wrapper"><x-ocaml>of_string_nav "foo";;</x-ocaml></div><p>The RFC is strict: non-empty pointers MUST start with <code>/</code>.</p><p>For safer parsing, use <code>of_string_result</code>:</p><div class="x-ocaml-wrapper"><x-ocaml>of_string_result "foo";; 42 - of_string_result "/valid";;</x-ocaml></div><h2 id="evaluation:-navigating-json"><a href="#evaluation:-navigating-json" class="anchor"></a>Evaluation: Navigating JSON</h2><p>Now we come to the heart of JSON Pointer: evaluation. <a href="https://datatracker.ietf.org/doc/html/rfc6901#section-4">RFC 6901, Section 4</a> describes how a pointer is resolved against a JSON document:</p><p><i>Evaluation of a JSON Pointer begins with a reference to the root value of a JSON document and completes with a reference to some value within the document. Each reference token in the JSON Pointer is evaluated sequentially.</i></p><p>Let's use the example JSON document from <a href="https://datatracker.ietf.org/doc/html/rfc6901#section-5">RFC 6901, Section 5</a>:</p><div class="x-ocaml-wrapper"><x-ocaml>let rfc_example = parse_json {|{ 41 + | Jsont.Path.Nth of int * Jsont.Meta.t *)</pre><p>The <code>Mem</code> constructor is for object member access, and <code>Nth</code> is for array index access. The member name is <b>unescaped</b> - you work with the actual key string (like <code>"a/b"</code>) and the library handles any escaping needed for the JSON Pointer string representation.</p><h3 id="invalid-syntax"><a href="#invalid-syntax" class="anchor"></a>Invalid Syntax</h3><p>What happens if a pointer doesn't start with <code>/</code>?</p><div><div class="x-ocaml-wrapper"><x-ocaml>of_string_nav "foo";;</x-ocaml></div></div><p>The RFC is strict: non-empty pointers MUST start with <code>/</code>.</p><p>For safer parsing, use <code>of_string_result</code>:</p><div><div class="x-ocaml-wrapper"><x-ocaml>of_string_result "foo";; 42 + of_string_result "/valid";;</x-ocaml></div></div><h2 id="evaluation:-navigating-json"><a href="#evaluation:-navigating-json" class="anchor"></a>Evaluation: Navigating JSON</h2><p>Now we come to the heart of JSON Pointer: evaluation. <a href="https://datatracker.ietf.org/doc/html/rfc6901#section-4">RFC 6901, Section 4</a> describes how a pointer is resolved against a JSON document:</p><p><i>Evaluation of a JSON Pointer begins with a reference to the root value of a JSON document and completes with a reference to some value within the document. Each reference token in the JSON Pointer is evaluated sequentially.</i></p><p>Let's use the example JSON document from <a href="https://datatracker.ietf.org/doc/html/rfc6901#section-5">RFC 6901, Section 5</a>:</p><div><div class="x-ocaml-wrapper"><x-ocaml>let rfc_example = parse_json {|{ 43 43 "foo": ["bar", "baz"], 44 44 "": 0, 45 45 "a/b": 1, ··· 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>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 - 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"];; 53 + }|};;</x-ocaml></div></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><div class="x-ocaml-wrapper"><x-ocaml>get root rfc_example ;;</x-ocaml></div></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><div class="x-ocaml-wrapper"><x-ocaml>get (of_string_nav "/foo") rfc_example ;;</x-ocaml></div></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><div class="x-ocaml-wrapper"><x-ocaml>get (of_string_nav "/foo/0") rfc_example ;; 54 + get (of_string_nav "/foo/1") rfc_example ;;</x-ocaml></div></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><div class="x-ocaml-wrapper"><x-ocaml>get (of_string_nav "/") rfc_example ;;</x-ocaml></div></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><div class="x-ocaml-wrapper"><x-ocaml>get (of_string_nav "/a~1b") rfc_example ;;</x-ocaml></div></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><div class="x-ocaml-wrapper"><x-ocaml>get (of_string_nav "/m~0n") rfc_example ;;</x-ocaml></div></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><div class="x-ocaml-wrapper"><x-ocaml>let slash_ptr = make [mem "a/b"];; 55 55 to_string slash_ptr;; 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 ;; 56 + get slash_ptr rfc_example ;;</x-ocaml></div></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><div class="x-ocaml-wrapper"><x-ocaml>get (of_string_nav "/c%d") rfc_example ;; 57 57 get (of_string_nav "/e^f") rfc_example ;; 58 58 get (of_string_nav "/g|h") rfc_example ;; 59 - get (of_string_nav "/ ") rfc_example ;;</x-ocaml></div><p>Even a space is a valid key character!</p><h3 id="error-conditions"><a href="#error-conditions" class="anchor"></a>Error Conditions</h3><p>What happens when we try to access something that doesn't exist?</p><div class="x-ocaml-wrapper"><x-ocaml>get_result (of_string_nav "/nonexistent") rfc_example;; 60 - find (of_string_nav "/nonexistent") rfc_example;;</x-ocaml></div><p>Or an out-of-bounds array index:</p><div class="x-ocaml-wrapper"><x-ocaml>find (of_string_nav "/foo/99") rfc_example;;</x-ocaml></div><p>Or try to index into a non-container:</p><div class="x-ocaml-wrapper"><x-ocaml>find (of_string_nav "/foo/0/invalid") rfc_example;;</x-ocaml></div><p>The library provides both exception-raising and result-returning variants:</p><pre>val get : nav t -&gt; Jsont.json -&gt; Jsont.json 59 + get (of_string_nav "/ ") rfc_example ;;</x-ocaml></div></div><p>Even a space is a valid key character!</p><h3 id="error-conditions"><a href="#error-conditions" class="anchor"></a>Error Conditions</h3><p>What happens when we try to access something that doesn't exist?</p><div><div class="x-ocaml-wrapper"><x-ocaml>get_result (of_string_nav "/nonexistent") rfc_example;; 60 + find (of_string_nav "/nonexistent") rfc_example;;</x-ocaml></div></div><p>Or an out-of-bounds array index:</p><div><div class="x-ocaml-wrapper"><x-ocaml>find (of_string_nav "/foo/99") rfc_example;;</x-ocaml></div></div><p>Or try to index into a non-container:</p><div><div class="x-ocaml-wrapper"><x-ocaml>find (of_string_nav "/foo/0/invalid") rfc_example;;</x-ocaml></div></div><p>The library provides both exception-raising and result-returning variants:</p><pre>val get : nav t -&gt; Jsont.json -&gt; Jsont.json 61 61 val get_result : nav t -&gt; Jsont.json -&gt; (Jsont.json, Jsont.Error.t) result 62 - val find : nav t -&gt; Jsont.json -&gt; 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 *) 62 + val find : nav t -&gt; Jsont.json -&gt; 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><div class="x-ocaml-wrapper"><x-ocaml>of_string_nav "/foo/0";;</x-ocaml></div></div><p>Zero itself is fine.</p><div><div class="x-ocaml-wrapper"><x-ocaml>of_string_nav "/foo/01";;</x-ocaml></div></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>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";; 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><div class="x-ocaml-wrapper"><x-ocaml>of_string "/foo/0";; 66 + of_string "/foo/-";;</x-ocaml></div></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><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></div><p>For retrieval operations, use <code>Json_pointer.of_string_nav</code> which ensures the pointer doesn't contain <code>-</code>:</p><div><div class="x-ocaml-wrapper"><x-ocaml>of_string_nav "/foo/0";; 68 + of_string_nav "/foo/-";;</x-ocaml></div></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><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>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"]}|};; 70 + to_string app_ptr;;</x-ocaml></div></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><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></div><p>For arrays, <code>Json_pointer.add</code> inserts BEFORE the specified index:</p><div><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></div><p>This is where the <code>-</code> marker shines - it appends to the end:</p><div><div class="x-ocaml-wrapper"><x-ocaml>add (of_string "/foo/-") arr_obj ~value:(Jsont.Json.string "c");;</x-ocaml></div></div><p>You can also use <code>Json_pointer.at_end</code> to create an append pointer programmatically:</p><div><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></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><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>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 - 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>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 - 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"];; 74 + add (of_string "/items/-") items ~value:(Jsont.Json.string "z");;</x-ocaml></div></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><div class="x-ocaml-wrapper"><x-ocaml>let two_fields = parse_json {|{"foo":"bar","baz":"qux"}|};; 75 + remove (of_string_nav "/baz") two_fields ;;</x-ocaml></div></div><p>For arrays, it removes and shifts:</p><div><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></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><div class="x-ocaml-wrapper"><x-ocaml>replace (of_string_nav "/foo") obj ~value:(Jsont.Json.string "baz") 77 + ;;</x-ocaml></div></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><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></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><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></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><div class="x-ocaml-wrapper"><x-ocaml>test (of_string_nav "/foo") obj ~expected:(Jsont.Json.string "bar");; 80 + test (of_string_nav "/foo") obj ~expected:(Jsont.Json.string "wrong");;</x-ocaml></div></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><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>Json_pointer.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></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><div class="x-ocaml-wrapper"><x-ocaml>Token.escape "hello";; 83 83 Token.escape "a/b";; 84 84 Token.escape "a~b";; 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";; 86 - Token.unescape "a~0b";;</x-ocaml></div><h3 id="the-order-matters!"><a href="#the-order-matters!" class="anchor"></a>The Order Matters!</h3><p><a href="https://datatracker.ietf.org/doc/html/rfc6901#section-4">RFC 6901, Section 4</a> is careful to specify the unescaping order:</p><p><i>Evaluation of each reference token begins by decoding any escaped character sequence. This is performed by first transforming any occurrence of the sequence '~1' to '/', and then transforming any occurrence of the sequence '~0' to '~'. By performing the substitutions in this order, an implementation avoids the error of turning '~01' first into '~1' and then into '/', which would be incorrect (the string '~01' correctly becomes '~1' after transformation).</i></p><p>Let's verify this tricky case:</p><div class="x-ocaml-wrapper"><x-ocaml>Token.unescape "~01";;</x-ocaml></div><p>If we unescaped <code>~0</code> first, <code>~01</code> would become <code>~1</code>, which would then become <code>/</code>. But that's wrong! The sequence <code>~01</code> should become the literal string <code>~1</code> (a tilde followed by the digit one).</p><h2 id="uri-fragment-encoding"><a href="#uri-fragment-encoding" class="anchor"></a>URI Fragment Encoding</h2><p>JSON Pointers can be embedded in URIs. <a href="https://datatracker.ietf.org/doc/html/rfc6901#section-6">RFC 6901, Section 6</a> explains:</p><p><i>A JSON Pointer can be represented in a URI fragment identifier by encoding it into octets using UTF-8, while percent-encoding those characters not allowed by the fragment rule in <a href="https://datatracker.ietf.org/doc/html/rfc3986">RFC 3986</a>.</i></p><p>This adds percent-encoding on top of the <code>~0</code>/<code>~1</code> escaping:</p><div class="x-ocaml-wrapper"><x-ocaml>to_uri_fragment (of_string_nav "/foo");; 85 + Token.escape "~/";;</x-ocaml></div></div><h3 id="unescaping"><a href="#unescaping" class="anchor"></a>Unescaping</h3><p>And the reverse process:</p><div><div class="x-ocaml-wrapper"><x-ocaml>Token.unescape "a~1b";; 86 + Token.unescape "a~0b";;</x-ocaml></div></div><h3 id="the-order-matters!"><a href="#the-order-matters!" class="anchor"></a>The Order Matters!</h3><p><a href="https://datatracker.ietf.org/doc/html/rfc6901#section-4">RFC 6901, Section 4</a> is careful to specify the unescaping order:</p><p><i>Evaluation of each reference token begins by decoding any escaped character sequence. This is performed by first transforming any occurrence of the sequence '~1' to '/', and then transforming any occurrence of the sequence '~0' to '~'. By performing the substitutions in this order, an implementation avoids the error of turning '~01' first into '~1' and then into '/', which would be incorrect (the string '~01' correctly becomes '~1' after transformation).</i></p><p>Let's verify this tricky case:</p><div><div class="x-ocaml-wrapper"><x-ocaml>Token.unescape "~01";;</x-ocaml></div></div><p>If we unescaped <code>~0</code> first, <code>~01</code> would become <code>~1</code>, which would then become <code>/</code>. But that's wrong! The sequence <code>~01</code> should become the literal string <code>~1</code> (a tilde followed by the digit one).</p><h2 id="uri-fragment-encoding"><a href="#uri-fragment-encoding" class="anchor"></a>URI Fragment Encoding</h2><p>JSON Pointers can be embedded in URIs. <a href="https://datatracker.ietf.org/doc/html/rfc6901#section-6">RFC 6901, Section 6</a> explains:</p><p><i>A JSON Pointer can be represented in a URI fragment identifier by encoding it into octets using UTF-8, while percent-encoding those characters not allowed by the fragment rule in <a href="https://datatracker.ietf.org/doc/html/rfc3986">RFC 3986</a>.</i></p><p>This adds percent-encoding on top of the <code>~0</code>/<code>~1</code> escaping:</p><div><div class="x-ocaml-wrapper"><x-ocaml>to_uri_fragment (of_string_nav "/foo");; 87 87 to_uri_fragment (of_string_nav "/a~1b");; 88 88 to_uri_fragment (of_string_nav "/c%d");; 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> -&gt; <code>#</code> -&gt; whole document</li><li><code>"/foo"</code> -&gt; <code>#/foo</code> -&gt; <code>["bar", "baz"]</code></li><li><code>"/foo/0"</code> -&gt; <code>#/foo/0</code> -&gt; <code>"bar"</code></li><li><code>"/"</code> -&gt; <code>#/</code> -&gt; <code>0</code></li><li><code>"/a~1b"</code> -&gt; <code>#/a~1b</code> -&gt; <code>1</code></li><li><code>"/c%d"</code> -&gt; <code>#/c%25d</code> -&gt; <code>2</code></li><li><code>"/ "</code> -&gt; <code>#/%20</code> -&gt; <code>7</code></li><li><code>"/m~0n"</code> -&gt; <code>#/m~0n</code> -&gt; <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>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";; 89 + to_uri_fragment (of_string_nav "/ ");;</x-ocaml></div></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> -&gt; <code>#</code> -&gt; whole document</li><li><code>"/foo"</code> -&gt; <code>#/foo</code> -&gt; <code>["bar", "baz"]</code></li><li><code>"/foo/0"</code> -&gt; <code>#/foo/0</code> -&gt; <code>"bar"</code></li><li><code>"/"</code> -&gt; <code>#/</code> -&gt; <code>0</code></li><li><code>"/a~1b"</code> -&gt; <code>#/a~1b</code> -&gt; <code>1</code></li><li><code>"/c%d"</code> -&gt; <code>#/c%25d</code> -&gt; <code>2</code></li><li><code>"/ "</code> -&gt; <code>#/%20</code> -&gt; <code>7</code></li><li><code>"/m~0n"</code> -&gt; <code>#/m~0n</code> -&gt; <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><div class="x-ocaml-wrapper"><x-ocaml>let port_ptr = make [mem "database"; mem "port"];; 90 + to_string port_ptr;;</x-ocaml></div></div><p>For array access, use the <code>Json_pointer.nth</code> helper:</p><div><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></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><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 - 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";; 94 + to_string user_ptr;;</x-ocaml></div></div><p>Or concatenate two pointers:</p><div><div class="x-ocaml-wrapper"><x-ocaml>let base = of_string_nav "/api/v1";; 95 95 let endpoint = of_string_nav "/users/0";; 96 - to_string (concat base endpoint);;</x-ocaml></div><h2 id="jsont-integration"><a href="#jsont-integration" class="anchor"></a>Jsont Integration</h2><p>The library integrates with the <code>Jsont</code> codec system, allowing you to combine JSON Pointer navigation with typed decoding. This is powerful because you can point to a location in a JSON document and decode it directly to an OCaml type.</p><div class="x-ocaml-wrapper"><x-ocaml>let config_json = parse_json {|{ 96 + to_string (concat base endpoint);;</x-ocaml></div></div><h2 id="jsont-integration"><a href="#jsont-integration" class="anchor"></a>Jsont Integration</h2><p>The library integrates with the <code>Jsont</code> codec system, allowing you to combine JSON Pointer navigation with typed decoding. This is powerful because you can point to a location in a JSON document and decode it directly to an OCaml type.</p><div><div class="x-ocaml-wrapper"><x-ocaml>let config_json = parse_json {|{ 97 97 "database": { 98 98 "host": "localhost", 99 99 "port": 5432, 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>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";; 103 + }|};;</x-ocaml></div></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><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) ··· 110 110 Jsont.Json.decode 111 111 (path (of_string_nav "/database/port") Jsont.int) 112 112 config_json 113 - |&gt; Result.get_ok;;</x-ocaml></div><p>Extract a list of strings:</p><div class="x-ocaml-wrapper"><x-ocaml>let features = 113 + |&gt; Result.get_ok;;</x-ocaml></div></div><p>Extract a list of strings:</p><div><div class="x-ocaml-wrapper"><x-ocaml>let features = 114 114 Jsont.Json.decode 115 115 (path (of_string_nav "/features") Jsont.(list string)) 116 116 config_json 117 - |&gt; Result.get_ok;;</x-ocaml></div><h3 id="default-values-with-~absent"><a href="#default-values-with-~absent" class="anchor"></a>Default Values with <code>~absent</code></h3><p>Use <code>~absent</code> to provide a default when a path doesn't exist:</p><div class="x-ocaml-wrapper"><x-ocaml>let timeout = 117 + |&gt; Result.get_ok;;</x-ocaml></div></div><h3 id="default-values-with-~absent"><a href="#default-values-with-~absent" class="anchor"></a>Default Values with <code>~absent</code></h3><p>Use <code>~absent</code> to provide a default when a path doesn't exist:</p><div><div class="x-ocaml-wrapper"><x-ocaml>let timeout = 118 118 Jsont.Json.decode 119 119 (path ~absent:30 (of_string_nav "/database/timeout") Jsont.int) 120 120 config_json 121 - |&gt; Result.get_ok;;</x-ocaml></div><h3 id="nested-path-extraction"><a href="#nested-path-extraction" class="anchor"></a>Nested Path Extraction</h3><p>You can extract values from deeply nested structures:</p><div class="x-ocaml-wrapper"><x-ocaml>let org_json = parse_json {|{ 121 + |&gt; Result.get_ok;;</x-ocaml></div></div><h3 id="nested-path-extraction"><a href="#nested-path-extraction" class="anchor"></a>Nested Path Extraction</h3><p>You can extract values from deeply nested structures:</p><div><div class="x-ocaml-wrapper"><x-ocaml>let org_json = parse_json {|{ 122 122 "organization": { 123 123 "owner": {"name": "Alice", "email": "alice@example.com", "age": 35}, 124 124 "members": [{"name": "Bob", "email": "bob@example.com", "age": 28}] ··· 131 131 Jsont.Json.decode 132 132 (path (of_string_nav "/organization/members/0/age") Jsont.int) 133 133 org_json 134 - |&gt; Result.get_ok;;</x-ocaml></div><h3 id="comparison:-raw-vs-typed-access"><a href="#comparison:-raw-vs-typed-access" class="anchor"></a>Comparison: Raw vs Typed Access</h3><p><b>Raw access</b> requires pattern matching:</p><div class="x-ocaml-wrapper"><x-ocaml>let raw_port = 134 + |&gt; Result.get_ok;;</x-ocaml></div></div><h3 id="comparison:-raw-vs-typed-access"><a href="#comparison:-raw-vs-typed-access" class="anchor"></a>Comparison: Raw vs Typed Access</h3><p><b>Raw access</b> requires pattern matching:</p><div><div class="x-ocaml-wrapper"><x-ocaml>let raw_port = 135 135 match get (of_string_nav "/database/port") config_json with 136 136 | Jsont.Number (f, _) -&gt; int_of_float f 137 - | _ -&gt; failwith "expected number";;</x-ocaml></div><p><b>Typed access</b> is cleaner and type-safe:</p><div class="x-ocaml-wrapper"><x-ocaml>let typed_port = 137 + | _ -&gt; failwith "expected number";;</x-ocaml></div></div><p><b>Typed access</b> is cleaner and type-safe:</p><div><div class="x-ocaml-wrapper"><x-ocaml>let typed_port = 138 138 Jsont.Json.decode 139 139 (path (of_string_nav "/database/port") Jsont.int) 140 140 config_json 141 - |&gt; 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"]}|};; 141 + |&gt; Result.get_ok;;</x-ocaml></div></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><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>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 - 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> 143 + set (of_string "/tasks/-") tasks ~value:(Jsont.Json.string "call mom");;</x-ocaml></div></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><div class="x-ocaml-wrapper"><x-ocaml>of_string_kind "/tasks/0";; 144 + of_string_kind "/tasks/-";;</x-ocaml></div></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>
+61 -61
doc/tutorial.html
··· 1 1 <!DOCTYPE html> 2 - <html xmlns="http://www.w3.org/1999/xhtml"><head><title>tutorial (tutorial)</title><meta charset="utf-8"/><link rel="stylesheet" href="odoc.css"/><meta name="generator" content="odoc %%VERSION%%"/><meta name="viewport" content="width=device-width,initial-scale=1.0"/><script src="highlight.pack.js"></script><script>hljs.initHighlightingOnLoad();</script></head><body class="odoc"><nav class="odoc-nav"><a href="index.html">Up</a> – <a href="index.html">Index</a> &#x00BB; tutorial</nav><header class="odoc-preamble"><h1 id="json-pointer-tutorial"><a href="#json-pointer-tutorial" class="anchor"></a>JSON Pointer Tutorial</h1><p>This tutorial introduces JSON Pointer as defined in <a href="https://www.rfc-editor.org/rfc/rfc6901">RFC 6901</a>, and demonstrates the <code>json-pointer</code> OCaml library through interactive examples.</p></header><div class="odoc-tocs"><nav class="odoc-toc odoc-local-toc"><ul><li><a href="#json-pointer-vs-json-path">JSON Pointer vs JSON Path</a></li><li><a href="#setup">Setup</a></li><li><a href="#what-is-json-pointer?">What is JSON Pointer?</a></li><li><a href="#syntax:-reference-tokens">Syntax: Reference Tokens</a><ul><li><a href="#the-index-type">The Index Type</a></li><li><a href="#invalid-syntax">Invalid Syntax</a></li></ul></li><li><a href="#evaluation:-navigating-json">Evaluation: Navigating JSON</a><ul><li><a href="#the-root-pointer">The Root Pointer</a></li><li><a href="#object-member-access">Object Member Access</a></li><li><a href="#array-index-access">Array Index Access</a></li><li><a href="#empty-string-as-key">Empty String as Key</a></li><li><a href="#keys-with-special-characters">Keys with Special Characters</a></li><li><a href="#other-special-characters-(no-escaping-needed)">Other Special Characters (No Escaping Needed)</a></li><li><a href="#error-conditions">Error Conditions</a></li><li><a href="#array-index-rules">Array Index Rules</a></li></ul></li><li><a href="#the-end-of-array-marker:---and-type-safety">The End-of-Array Marker: <code>-</code> and Type Safety</a><ul><li><a href="#navigation-vs-append-pointers">Navigation vs Append Pointers</a></li><li><a href="#why-two-pointer-types?">Why Two Pointer Types?</a></li><li><a href="#creating-append-pointers-programmatically">Creating Append Pointers Programmatically</a></li></ul></li><li><a href="#mutation-operations">Mutation Operations</a><ul><li><a href="#add">Add</a></li><li><a href="#ergonomic-mutation-with-any">Ergonomic Mutation with <code>any</code></a></li><li><a href="#remove">Remove</a></li><li><a href="#replace">Replace</a></li><li><a href="#move">Move</a></li><li><a href="#copy">Copy</a></li><li><a href="#test">Test</a></li></ul></li><li><a href="#escaping">Escaping Special Characters</a><ul><li><a href="#the-library-handles-escaping-automatically">The Library Handles Escaping Automatically</a></li><li><a href="#escaping-in-action">Escaping in Action</a></li><li><a href="#unescaping">Unescaping</a></li><li><a href="#the-order-matters!">The Order Matters!</a></li></ul></li><li><a href="#uri-fragment-encoding">URI Fragment Encoding</a></li><li><a href="#building-pointers-programmatically">Building Pointers Programmatically</a><ul><li><a href="#pointer-navigation">Pointer Navigation</a></li></ul></li><li><a href="#jsont-integration">Jsont Integration</a><ul><li><a href="#typed-access-with-path">Typed Access with <code>path</code></a></li><li><a href="#default-values-with-~absent">Default Values with <code>~absent</code></a></li><li><a href="#nested-path-extraction">Nested Path Extraction</a></li><li><a href="#comparison:-raw-vs-typed-access">Comparison: Raw vs Typed Access</a></li><li><a href="#updates-with-polymorphic-pointers">Updates with Polymorphic Pointers</a></li></ul></li><li><a href="#summary">Summary</a><ul><li><a href="#key-points-on-json-pointer-vs-json-path">Key Points on JSON Pointer vs JSON Path</a></li></ul></li></ul></nav></div><div class="odoc-content"><h2 id="json-pointer-vs-json-path"><a href="#json-pointer-vs-json-path" class="anchor"></a>JSON Pointer vs JSON Path</h2><p>Before diving in, it's worth understanding the difference between JSON Pointer and JSON Path, as they serve different purposes:</p><p><b>JSON Pointer</b> (<a href="https://datatracker.ietf.org/doc/html/rfc6901">RFC 6901</a>) is an <em>indicator syntax</em> that specifies a <em>single location</em> within JSON data. It always identifies at most one value.</p><p><b>JSON Path</b> is a <em>query syntax</em> that can <em>search</em> JSON data and return <em>multiple</em> values matching specified criteria.</p><p>Use JSON Pointer when you need to address a single, specific location (like JSON Schema's <code>$ref</code>). Use JSON Path when you might need multiple results (like Kubernetes queries).</p><p>The <code>json-pointer</code> library implements JSON Pointer and integrates with the <code>Jsont.Path</code> type for representing navigation indices.</p><h2 id="setup"><a href="#setup" class="anchor"></a>Setup</h2><p>First, let's set up our environment. In the toplevel, you can load the library with <code>#require &quot;json-pointer.top&quot;;;</code> which will automatically install pretty printers.</p><pre class="language-ocaml"><code># Json_pointer_top.install ();; 2 + <html xmlns="http://www.w3.org/1999/xhtml"><head><title>tutorial (tutorial)</title><meta charset="utf-8"/><link rel="stylesheet" href="odoc.css"/><meta name="generator" content="odoc %%VERSION%%"/><meta name="viewport" content="width=device-width,initial-scale=1.0"/><script src="highlight.pack.js"></script><script>hljs.initHighlightingOnLoad();</script></head><body class="odoc"><nav class="odoc-nav"><a href="index.html">Up</a> – <a href="index.html">Index</a> &#x00BB; tutorial</nav><header class="odoc-preamble"><h1 id="json-pointer-tutorial"><a href="#json-pointer-tutorial" class="anchor"></a>JSON Pointer Tutorial</h1><p>This tutorial introduces JSON Pointer as defined in <a href="https://www.rfc-editor.org/rfc/rfc6901">RFC 6901</a>, and demonstrates the <code>json-pointer</code> OCaml library through interactive examples.</p></header><div class="odoc-tocs"><nav class="odoc-toc odoc-local-toc"><ul><li><a href="#json-pointer-vs-json-path">JSON Pointer vs JSON Path</a></li><li><a href="#setup">Setup</a></li><li><a href="#what-is-json-pointer?">What is JSON Pointer?</a></li><li><a href="#syntax:-reference-tokens">Syntax: Reference Tokens</a><ul><li><a href="#the-index-type">The Index Type</a></li><li><a href="#invalid-syntax">Invalid Syntax</a></li></ul></li><li><a href="#evaluation:-navigating-json">Evaluation: Navigating JSON</a><ul><li><a href="#the-root-pointer">The Root Pointer</a></li><li><a href="#object-member-access">Object Member Access</a></li><li><a href="#array-index-access">Array Index Access</a></li><li><a href="#empty-string-as-key">Empty String as Key</a></li><li><a href="#keys-with-special-characters">Keys with Special Characters</a></li><li><a href="#other-special-characters-(no-escaping-needed)">Other Special Characters (No Escaping Needed)</a></li><li><a href="#error-conditions">Error Conditions</a></li><li><a href="#array-index-rules">Array Index Rules</a></li></ul></li><li><a href="#the-end-of-array-marker:---and-type-safety">The End-of-Array Marker: <code>-</code> and Type Safety</a><ul><li><a href="#navigation-vs-append-pointers">Navigation vs Append Pointers</a></li><li><a href="#why-two-pointer-types?">Why Two Pointer Types?</a></li><li><a href="#creating-append-pointers-programmatically">Creating Append Pointers Programmatically</a></li></ul></li><li><a href="#mutation-operations">Mutation Operations</a><ul><li><a href="#add">Add</a></li><li><a href="#ergonomic-mutation-with-any">Ergonomic Mutation with <code>any</code></a></li><li><a href="#remove">Remove</a></li><li><a href="#replace">Replace</a></li><li><a href="#move">Move</a></li><li><a href="#copy">Copy</a></li><li><a href="#test">Test</a></li></ul></li><li><a href="#escaping">Escaping Special Characters</a><ul><li><a href="#the-library-handles-escaping-automatically">The Library Handles Escaping Automatically</a></li><li><a href="#escaping-in-action">Escaping in Action</a></li><li><a href="#unescaping">Unescaping</a></li><li><a href="#the-order-matters!">The Order Matters!</a></li></ul></li><li><a href="#uri-fragment-encoding">URI Fragment Encoding</a></li><li><a href="#building-pointers-programmatically">Building Pointers Programmatically</a><ul><li><a href="#pointer-navigation">Pointer Navigation</a></li></ul></li><li><a href="#jsont-integration">Jsont Integration</a><ul><li><a href="#typed-access-with-path">Typed Access with <code>path</code></a></li><li><a href="#default-values-with-~absent">Default Values with <code>~absent</code></a></li><li><a href="#nested-path-extraction">Nested Path Extraction</a></li><li><a href="#comparison:-raw-vs-typed-access">Comparison: Raw vs Typed Access</a></li><li><a href="#updates-with-polymorphic-pointers">Updates with Polymorphic Pointers</a></li></ul></li><li><a href="#summary">Summary</a><ul><li><a href="#key-points-on-json-pointer-vs-json-path">Key Points on JSON Pointer vs JSON Path</a></li></ul></li></ul></nav></div><div class="odoc-content"><h2 id="json-pointer-vs-json-path"><a href="#json-pointer-vs-json-path" class="anchor"></a>JSON Pointer vs JSON Path</h2><p>Before diving in, it's worth understanding the difference between JSON Pointer and JSON Path, as they serve different purposes:</p><p><b>JSON Pointer</b> (<a href="https://datatracker.ietf.org/doc/html/rfc6901">RFC 6901</a>) is an <em>indicator syntax</em> that specifies a <em>single location</em> within JSON data. It always identifies at most one value.</p><p><b>JSON Path</b> is a <em>query syntax</em> that can <em>search</em> JSON data and return <em>multiple</em> values matching specified criteria.</p><p>Use JSON Pointer when you need to address a single, specific location (like JSON Schema's <code>$ref</code>). Use JSON Path when you might need multiple results (like Kubernetes queries).</p><p>The <code>json-pointer</code> library implements JSON Pointer and integrates with the <code>Jsont.Path</code> type for representing navigation indices.</p><h2 id="setup"><a href="#setup" class="anchor"></a>Setup</h2><p>First, let's set up our environment. In the toplevel, you can load the library with <code>#require &quot;json-pointer.top&quot;;;</code> which will automatically install pretty printers.</p><div><pre class="language-ocaml"><code># Json_pointer_top.install ();; 3 3 - : unit = () 4 4 # open Json_pointer;; 5 5 # let parse_json s = 6 6 match Jsont_bytesrw.decode_string Jsont.json s with 7 7 | Ok json -&gt; json 8 8 | Error e -&gt; failwith e;; 9 - val parse_json : string -&gt; Jsont.json = &lt;fun&gt;</code></pre><h2 id="what-is-json-pointer?"><a href="#what-is-json-pointer?" class="anchor"></a>What is JSON Pointer?</h2><p>From <a href="https://datatracker.ietf.org/doc/html/rfc6901#section-1">RFC 6901, Section 1</a>:</p><p><i>JSON Pointer defines a string syntax for identifying a specific value within a JavaScript Object Notation (JSON) document.</i></p><p>In other words, JSON Pointer is an addressing scheme for locating values inside a JSON structure. Think of it like a filesystem path, but for JSON documents instead of files.</p><p>For example, given this JSON document:</p><pre class="language-ocaml"><code># let users_json = parse_json {|{ 9 + val parse_json : string -&gt; Jsont.json = &lt;fun&gt;</code></pre></div><h2 id="what-is-json-pointer?"><a href="#what-is-json-pointer?" class="anchor"></a>What is JSON Pointer?</h2><p>From <a href="https://datatracker.ietf.org/doc/html/rfc6901#section-1">RFC 6901, Section 1</a>:</p><p><i>JSON Pointer defines a string syntax for identifying a specific value within a JavaScript Object Notation (JSON) document.</i></p><p>In other words, JSON Pointer is an addressing scheme for locating values inside a JSON structure. Think of it like a filesystem path, but for JSON documents instead of files.</p><p>For example, given this JSON document:</p><div><pre class="language-ocaml"><code># let users_json = parse_json {|{ 10 10 &quot;users&quot;: [ 11 11 {&quot;name&quot;: &quot;Alice&quot;, &quot;age&quot;: 30}, 12 12 {&quot;name&quot;: &quot;Bob&quot;, &quot;age&quot;: 25} 13 13 ] 14 14 }|};; 15 15 val users_json : Jsont.json = 16 - {&quot;users&quot;:[{&quot;name&quot;:&quot;Alice&quot;,&quot;age&quot;:30},{&quot;name&quot;:&quot;Bob&quot;,&quot;age&quot;:25}]}</code></pre><p>The JSON Pointer <code>/users/0/name</code> refers to the string <code>&quot;Alice&quot;</code>:</p><pre class="language-ocaml"><code># let ptr = of_string_nav &quot;/users/0/name&quot;;; 16 + {&quot;users&quot;:[{&quot;name&quot;:&quot;Alice&quot;,&quot;age&quot;:30},{&quot;name&quot;:&quot;Bob&quot;,&quot;age&quot;:25}]}</code></pre></div><p>The JSON Pointer <code>/users/0/name</code> refers to the string <code>&quot;Alice&quot;</code>:</p><div><pre class="language-ocaml"><code># let ptr = of_string_nav &quot;/users/0/name&quot;;; 17 17 val ptr : nav t = [Mem &quot;users&quot;; Nth 0; Mem &quot;name&quot;] 18 18 # get ptr users_json;; 19 - - : Jsont.json = &quot;Alice&quot;</code></pre><p>In OCaml, this is represented by the <code>'a Json_pointer.t</code> type - a sequence of navigation steps from the document root to a target value. The phantom type parameter <code>'a</code> encodes whether this is a navigation pointer or an append pointer (more on this later).</p><h2 id="syntax:-reference-tokens"><a href="#syntax:-reference-tokens" class="anchor"></a>Syntax: Reference Tokens</h2><p><a href="https://datatracker.ietf.org/doc/html/rfc6901#section-3">RFC 6901, Section 3</a> defines the syntax:</p><p><i>A JSON Pointer is a Unicode string containing a sequence of zero or more reference tokens, each prefixed by a '/' (%x2F) character.</i></p><p>The grammar is elegantly simple:</p><pre>json-pointer = *( &quot;/&quot; reference-token ) 20 - reference-token = *( unescaped / escaped )</pre><p>This means:</p><ul><li>The empty string <code>&quot;&quot;</code> is a valid pointer (it refers to the whole document)</li><li>Every non-empty pointer starts with <code>/</code></li><li>Everything between <code>/</code> characters is a &quot;reference token&quot;</li></ul><p>Let's see this in action:</p><pre class="language-ocaml"><code># of_string_nav &quot;&quot;;; 21 - - : nav t = []</code></pre><p>The empty pointer has no reference tokens - it points to the root.</p><pre class="language-ocaml"><code># of_string_nav &quot;/foo&quot;;; 22 - - : nav t = [Mem &quot;foo&quot;]</code></pre><p>The pointer <code>/foo</code> has one token: <code>foo</code>. Since it's not a number, it's interpreted as an object member name (<code>Mem</code>).</p><pre class="language-ocaml"><code># of_string_nav &quot;/foo/0&quot;;; 23 - - : nav t = [Mem &quot;foo&quot;; Nth 0]</code></pre><p>Here we have two tokens: <code>foo</code> (a member name) and <code>0</code> (interpreted as an array index <code>Nth</code>).</p><pre class="language-ocaml"><code># of_string_nav &quot;/foo/bar/baz&quot;;; 24 - - : nav t = [Mem &quot;foo&quot;; Mem &quot;bar&quot;; Mem &quot;baz&quot;]</code></pre><p>Multiple tokens navigate deeper into nested structures.</p><h3 id="the-index-type"><a href="#the-index-type" class="anchor"></a>The Index Type</h3><p>Each reference token is represented using <code>Jsont.Path.index</code>:</p><pre>type index = Jsont.Path.index 19 + - : Jsont.json = &quot;Alice&quot;</code></pre></div><p>In OCaml, this is represented by the <code>'a Json_pointer.t</code> type - a sequence of navigation steps from the document root to a target value. The phantom type parameter <code>'a</code> encodes whether this is a navigation pointer or an append pointer (more on this later).</p><h2 id="syntax:-reference-tokens"><a href="#syntax:-reference-tokens" class="anchor"></a>Syntax: Reference Tokens</h2><p><a href="https://datatracker.ietf.org/doc/html/rfc6901#section-3">RFC 6901, Section 3</a> defines the syntax:</p><p><i>A JSON Pointer is a Unicode string containing a sequence of zero or more reference tokens, each prefixed by a '/' (%x2F) character.</i></p><p>The grammar is elegantly simple:</p><pre>json-pointer = *( &quot;/&quot; reference-token ) 20 + reference-token = *( unescaped / escaped )</pre><p>This means:</p><ul><li>The empty string <code>&quot;&quot;</code> is a valid pointer (it refers to the whole document)</li><li>Every non-empty pointer starts with <code>/</code></li><li>Everything between <code>/</code> characters is a &quot;reference token&quot;</li></ul><p>Let's see this in action:</p><div><pre class="language-ocaml"><code># of_string_nav &quot;&quot;;; 21 + - : nav t = []</code></pre></div><p>The empty pointer has no reference tokens - it points to the root.</p><div><pre class="language-ocaml"><code># of_string_nav &quot;/foo&quot;;; 22 + - : nav t = [Mem &quot;foo&quot;]</code></pre></div><p>The pointer <code>/foo</code> has one token: <code>foo</code>. Since it's not a number, it's interpreted as an object member name (<code>Mem</code>).</p><div><pre class="language-ocaml"><code># of_string_nav &quot;/foo/0&quot;;; 23 + - : nav t = [Mem &quot;foo&quot;; Nth 0]</code></pre></div><p>Here we have two tokens: <code>foo</code> (a member name) and <code>0</code> (interpreted as an array index <code>Nth</code>).</p><div><pre class="language-ocaml"><code># of_string_nav &quot;/foo/bar/baz&quot;;; 24 + - : nav t = [Mem &quot;foo&quot;; Mem &quot;bar&quot;; Mem &quot;baz&quot;]</code></pre></div><p>Multiple tokens navigate deeper into nested structures.</p><h3 id="the-index-type"><a href="#the-index-type" class="anchor"></a>The Index Type</h3><p>Each reference token is represented using <code>Jsont.Path.index</code>:</p><pre>type index = Jsont.Path.index 25 25 (* = Jsont.Path.Mem of string * Jsont.Meta.t 26 - | Jsont.Path.Nth of int * Jsont.Meta.t *)</pre><p>The <code>Mem</code> constructor is for object member access, and <code>Nth</code> is for array index access. The member name is <b>unescaped</b> - you work with the actual key string (like <code>&quot;a/b&quot;</code>) and the library handles any escaping needed for the JSON Pointer string representation.</p><h3 id="invalid-syntax"><a href="#invalid-syntax" class="anchor"></a>Invalid Syntax</h3><p>What happens if a pointer doesn't start with <code>/</code>?</p><pre class="language-ocaml"><code># of_string_nav &quot;foo&quot;;; 26 + | Jsont.Path.Nth of int * Jsont.Meta.t *)</pre><p>The <code>Mem</code> constructor is for object member access, and <code>Nth</code> is for array index access. The member name is <b>unescaped</b> - you work with the actual key string (like <code>&quot;a/b&quot;</code>) and the library handles any escaping needed for the JSON Pointer string representation.</p><h3 id="invalid-syntax"><a href="#invalid-syntax" class="anchor"></a>Invalid Syntax</h3><p>What happens if a pointer doesn't start with <code>/</code>?</p><div><pre class="language-ocaml"><code># of_string_nav &quot;foo&quot;;; 27 27 Exception: 28 - Jsont.Error Invalid JSON Pointer: must be empty or start with '/': foo.</code></pre><p>The RFC is strict: non-empty pointers MUST start with <code>/</code>.</p><p>For safer parsing, use <code>of_string_result</code>:</p><pre class="language-ocaml"><code># of_string_result &quot;foo&quot;;; 28 + Jsont.Error Invalid JSON Pointer: must be empty or start with '/': foo.</code></pre></div><p>The RFC is strict: non-empty pointers MUST start with <code>/</code>.</p><p>For safer parsing, use <code>of_string_result</code>:</p><div><pre class="language-ocaml"><code># of_string_result &quot;foo&quot;;; 29 29 - : (any, string) result = 30 30 Error &quot;Invalid JSON Pointer: must be empty or start with '/': foo&quot; 31 31 # of_string_result &quot;/valid&quot;;; 32 - - : (any, string) result = Ok (Any &lt;abstr&gt;)</code></pre><h2 id="evaluation:-navigating-json"><a href="#evaluation:-navigating-json" class="anchor"></a>Evaluation: Navigating JSON</h2><p>Now we come to the heart of JSON Pointer: evaluation. <a href="https://datatracker.ietf.org/doc/html/rfc6901#section-4">RFC 6901, Section 4</a> describes how a pointer is resolved against a JSON document:</p><p><i>Evaluation of a JSON Pointer begins with a reference to the root value of a JSON document and completes with a reference to some value within the document. Each reference token in the JSON Pointer is evaluated sequentially.</i></p><p>Let's use the example JSON document from <a href="https://datatracker.ietf.org/doc/html/rfc6901#section-5">RFC 6901, Section 5</a>:</p><pre class="language-ocaml"><code># let rfc_example = parse_json {|{ 32 + - : (any, string) result = Ok (Any &lt;abstr&gt;)</code></pre></div><h2 id="evaluation:-navigating-json"><a href="#evaluation:-navigating-json" class="anchor"></a>Evaluation: Navigating JSON</h2><p>Now we come to the heart of JSON Pointer: evaluation. <a href="https://datatracker.ietf.org/doc/html/rfc6901#section-4">RFC 6901, Section 4</a> describes how a pointer is resolved against a JSON document:</p><p><i>Evaluation of a JSON Pointer begins with a reference to the root value of a JSON document and completes with a reference to some value within the document. Each reference token in the JSON Pointer is evaluated sequentially.</i></p><p>Let's use the example JSON document from <a href="https://datatracker.ietf.org/doc/html/rfc6901#section-5">RFC 6901, Section 5</a>:</p><div><pre class="language-ocaml"><code># let rfc_example = parse_json {|{ 33 33 &quot;foo&quot;: [&quot;bar&quot;, &quot;baz&quot;], 34 34 &quot;&quot;: 0, 35 35 &quot;a/b&quot;: 1, ··· 42 42 &quot;m~n&quot;: 8 43 43 }|};; 44 44 val rfc_example : Jsont.json = 45 - {&quot;foo&quot;:[&quot;bar&quot;,&quot;baz&quot;],&quot;&quot;:0,&quot;a/b&quot;:1,&quot;c%d&quot;:2,&quot;e^f&quot;:3,&quot;g|h&quot;:4,&quot;i\\j&quot;:5,&quot;k\&quot;l&quot;:6,&quot; &quot;:7,&quot;m~n&quot;: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 ;; 45 + {&quot;foo&quot;:[&quot;bar&quot;,&quot;baz&quot;],&quot;&quot;:0,&quot;a/b&quot;:1,&quot;c%d&quot;:2,&quot;e^f&quot;:3,&quot;g|h&quot;:4,&quot;i\\j&quot;:5,&quot;k\&quot;l&quot;:6,&quot; &quot;:7,&quot;m~n&quot;:8}</code></pre></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><pre class="language-ocaml"><code># get root rfc_example ;; 46 46 - : Jsont.json = 47 - {&quot;foo&quot;:[&quot;bar&quot;,&quot;baz&quot;],&quot;&quot;:0,&quot;a/b&quot;:1,&quot;c%d&quot;:2,&quot;e^f&quot;:3,&quot;g|h&quot;:4,&quot;i\\j&quot;:5,&quot;k\&quot;l&quot;:6,&quot; &quot;:7,&quot;m~n&quot;: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 &quot;/foo&quot;) rfc_example ;; 48 - - : Jsont.json = [&quot;bar&quot;,&quot;baz&quot;]</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 &quot;/foo/0&quot;) rfc_example ;; 47 + {&quot;foo&quot;:[&quot;bar&quot;,&quot;baz&quot;],&quot;&quot;:0,&quot;a/b&quot;:1,&quot;c%d&quot;:2,&quot;e^f&quot;:3,&quot;g|h&quot;:4,&quot;i\\j&quot;:5,&quot;k\&quot;l&quot;:6,&quot; &quot;:7,&quot;m~n&quot;:8}</code></pre></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><pre class="language-ocaml"><code># get (of_string_nav &quot;/foo&quot;) rfc_example ;; 48 + - : Jsont.json = [&quot;bar&quot;,&quot;baz&quot;]</code></pre></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><pre class="language-ocaml"><code># get (of_string_nav &quot;/foo/0&quot;) rfc_example ;; 49 49 - : Jsont.json = &quot;bar&quot; 50 50 # get (of_string_nav &quot;/foo/1&quot;) rfc_example ;; 51 - - : Jsont.json = &quot;baz&quot;</code></pre><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><pre class="language-ocaml"><code># get (of_string_nav &quot;/&quot;) rfc_example ;; 52 - - : Jsont.json = 0</code></pre><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><pre class="language-ocaml"><code># get (of_string_nav &quot;/a~1b&quot;) rfc_example ;; 53 - - : Jsont.json = 1</code></pre><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><pre class="language-ocaml"><code># get (of_string_nav &quot;/m~0n&quot;) rfc_example ;; 54 - - : Jsont.json = 8</code></pre><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><pre class="language-ocaml"><code># let slash_ptr = make [mem &quot;a/b&quot;];; 51 + - : Jsont.json = &quot;baz&quot;</code></pre></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><pre class="language-ocaml"><code># get (of_string_nav &quot;/&quot;) rfc_example ;; 52 + - : Jsont.json = 0</code></pre></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><pre class="language-ocaml"><code># get (of_string_nav &quot;/a~1b&quot;) rfc_example ;; 53 + - : Jsont.json = 1</code></pre></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><pre class="language-ocaml"><code># get (of_string_nav &quot;/m~0n&quot;) rfc_example ;; 54 + - : Jsont.json = 8</code></pre></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><pre class="language-ocaml"><code># let slash_ptr = make [mem &quot;a/b&quot;];; 55 55 val slash_ptr : nav t = [Mem &quot;a/b&quot;] 56 56 # to_string slash_ptr;; 57 57 - : string = &quot;/a~1b&quot; 58 58 # get slash_ptr rfc_example ;; 59 - - : Jsont.json = 1</code></pre><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><pre class="language-ocaml"><code># get (of_string_nav &quot;/c%d&quot;) rfc_example ;; 59 + - : Jsont.json = 1</code></pre></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><pre class="language-ocaml"><code># get (of_string_nav &quot;/c%d&quot;) rfc_example ;; 60 60 - : Jsont.json = 2 61 61 # get (of_string_nav &quot;/e^f&quot;) rfc_example ;; 62 62 - : Jsont.json = 3 63 63 # get (of_string_nav &quot;/g|h&quot;) rfc_example ;; 64 64 - : Jsont.json = 4 65 65 # get (of_string_nav &quot;/ &quot;) rfc_example ;; 66 - - : Jsont.json = 7</code></pre><p>Even a space is a valid key character!</p><h3 id="error-conditions"><a href="#error-conditions" class="anchor"></a>Error Conditions</h3><p>What happens when we try to access something that doesn't exist?</p><pre class="language-ocaml"><code># get_result (of_string_nav &quot;/nonexistent&quot;) rfc_example;; 66 + - : Jsont.json = 7</code></pre></div><p>Even a space is a valid key character!</p><h3 id="error-conditions"><a href="#error-conditions" class="anchor"></a>Error Conditions</h3><p>What happens when we try to access something that doesn't exist?</p><div><pre class="language-ocaml"><code># get_result (of_string_nav &quot;/nonexistent&quot;) rfc_example;; 67 67 - : (Jsont.json, Jsont.Error.t) result = 68 68 Error JSON Pointer: member 'nonexistent' not found 69 69 File &quot;-&quot;: 70 70 # find (of_string_nav &quot;/nonexistent&quot;) rfc_example;; 71 - - : Jsont.json option = None</code></pre><p>Or an out-of-bounds array index:</p><pre class="language-ocaml"><code># find (of_string_nav &quot;/foo/99&quot;) rfc_example;; 72 - - : Jsont.json option = None</code></pre><p>Or try to index into a non-container:</p><pre class="language-ocaml"><code># find (of_string_nav &quot;/foo/0/invalid&quot;) rfc_example;; 73 - - : Jsont.json option = None</code></pre><p>The library provides both exception-raising and result-returning variants:</p><pre>val get : nav t -&gt; Jsont.json -&gt; Jsont.json 71 + - : Jsont.json option = None</code></pre></div><p>Or an out-of-bounds array index:</p><div><pre class="language-ocaml"><code># find (of_string_nav &quot;/foo/99&quot;) rfc_example;; 72 + - : Jsont.json option = None</code></pre></div><p>Or try to index into a non-container:</p><div><pre class="language-ocaml"><code># find (of_string_nav &quot;/foo/0/invalid&quot;) rfc_example;; 73 + - : Jsont.json option = None</code></pre></div><p>The library provides both exception-raising and result-returning variants:</p><pre>val get : nav t -&gt; Jsont.json -&gt; Jsont.json 74 74 val get_result : nav t -&gt; Jsont.json -&gt; (Jsont.json, Jsont.Error.t) result 75 - val find : nav t -&gt; Jsont.json -&gt; 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><pre class="language-ocaml"><code># of_string_nav &quot;/foo/0&quot;;; 76 - - : nav t = [Mem &quot;foo&quot;; Nth 0]</code></pre><p>Zero itself is fine.</p><pre class="language-ocaml"><code># of_string_nav &quot;/foo/01&quot;;; 77 - - : nav t = [Mem &quot;foo&quot;; Mem &quot;01&quot;]</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 &quot;-&quot;, 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 &quot;append position&quot;:</p><pre>type nav (* A pointer to an existing element *) 75 + val find : nav t -&gt; Jsont.json -&gt; 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><pre class="language-ocaml"><code># of_string_nav &quot;/foo/0&quot;;; 76 + - : nav t = [Mem &quot;foo&quot;; Nth 0]</code></pre></div><p>Zero itself is fine.</p><div><pre class="language-ocaml"><code># of_string_nav &quot;/foo/01&quot;;; 77 + - : nav t = [Mem &quot;foo&quot;; Mem &quot;01&quot;]</code></pre></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 &quot;-&quot;, 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 &quot;append position&quot;:</p><pre>type nav (* A pointer to an existing element *) 78 78 type append (* A pointer ending with &quot;-&quot; (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>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 &quot;/foo/0&quot;;; 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><div><pre class="language-ocaml"><code># of_string &quot;/foo/0&quot;;; 81 81 - : any = Any &lt;abstr&gt; 82 82 # of_string &quot;/foo/-&quot;;; 83 - - : any = Any &lt;abstr&gt;</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 &quot;-&quot; 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 {|{&quot;foo&quot;:[&quot;a&quot;,&quot;b&quot;]}|};; 83 + - : any = Any &lt;abstr&gt;</code></pre></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 &quot;-&quot; 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><pre class="language-ocaml"><code># let arr_obj = parse_json {|{&quot;foo&quot;:[&quot;a&quot;,&quot;b&quot;]}|};; 84 84 val arr_obj : Jsont.json = {&quot;foo&quot;:[&quot;a&quot;,&quot;b&quot;]} 85 85 # add (of_string &quot;/foo/-&quot;) arr_obj ~value:(Jsont.Json.string &quot;c&quot;);; 86 - - : Jsont.json = {&quot;foo&quot;:[&quot;a&quot;,&quot;b&quot;,&quot;c&quot;]}</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 &quot;/foo/0&quot;;; 86 + - : Jsont.json = {&quot;foo&quot;:[&quot;a&quot;,&quot;b&quot;,&quot;c&quot;]}</code></pre></div><p>For retrieval operations, use <code>Json_pointer.of_string_nav</code> which ensures the pointer doesn't contain <code>-</code>:</p><div><pre class="language-ocaml"><code># of_string_nav &quot;/foo/0&quot;;; 87 87 - : nav t = [Mem &quot;foo&quot;; Nth 0] 88 88 # of_string_nav &quot;/foo/-&quot;;; 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>Json_pointer.at_end</code>:</p><pre class="language-ocaml"><code># let nav_ptr = of_string_nav &quot;/foo&quot;;; 90 + Jsont.Error Invalid JSON Pointer: '-' not allowed in navigation pointer.</code></pre></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><pre class="language-ocaml"><code># let nav_ptr = of_string_nav &quot;/foo&quot;;; 91 91 val nav_ptr : nav t = [Mem &quot;foo&quot;] 92 92 # let app_ptr = at_end nav_ptr;; 93 93 val app_ptr : append t = [Mem &quot;foo&quot;] /- 94 94 # to_string app_ptr;; 95 - - : string = &quot;/foo/-&quot;</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 {|{&quot;foo&quot;:&quot;bar&quot;}|};; 95 + - : string = &quot;/foo/-&quot;</code></pre></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><pre class="language-ocaml"><code># let obj = parse_json {|{&quot;foo&quot;:&quot;bar&quot;}|};; 96 96 val obj : Jsont.json = {&quot;foo&quot;:&quot;bar&quot;} 97 97 # add (of_string &quot;/baz&quot;) obj ~value:(Jsont.Json.string &quot;qux&quot;);; 98 - - : Jsont.json = {&quot;foo&quot;:&quot;bar&quot;,&quot;baz&quot;:&quot;qux&quot;}</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 {|{&quot;foo&quot;:[&quot;a&quot;,&quot;b&quot;]}|};; 98 + - : Jsont.json = {&quot;foo&quot;:&quot;bar&quot;,&quot;baz&quot;:&quot;qux&quot;}</code></pre></div><p>For arrays, <code>Json_pointer.add</code> inserts BEFORE the specified index:</p><div><pre class="language-ocaml"><code># let arr_obj = parse_json {|{&quot;foo&quot;:[&quot;a&quot;,&quot;b&quot;]}|};; 99 99 val arr_obj : Jsont.json = {&quot;foo&quot;:[&quot;a&quot;,&quot;b&quot;]} 100 100 # add (of_string &quot;/foo/1&quot;) arr_obj ~value:(Jsont.Json.string &quot;X&quot;);; 101 - - : Jsont.json = {&quot;foo&quot;:[&quot;a&quot;,&quot;X&quot;,&quot;b&quot;]}</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 &quot;/foo/-&quot;) arr_obj ~value:(Jsont.Json.string &quot;c&quot;);; 102 - - : Jsont.json = {&quot;foo&quot;:[&quot;a&quot;,&quot;b&quot;,&quot;c&quot;]}</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 &quot;/foo&quot;))) arr_obj ~value:(Jsont.Json.string &quot;c&quot;);; 103 - - : Jsont.json = {&quot;foo&quot;:[&quot;a&quot;,&quot;b&quot;,&quot;c&quot;]}</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 {|{&quot;items&quot;:[&quot;x&quot;]}|};; 101 + - : Jsont.json = {&quot;foo&quot;:[&quot;a&quot;,&quot;X&quot;,&quot;b&quot;]}</code></pre></div><p>This is where the <code>-</code> marker shines - it appends to the end:</p><div><pre class="language-ocaml"><code># add (of_string &quot;/foo/-&quot;) arr_obj ~value:(Jsont.Json.string &quot;c&quot;);; 102 + - : Jsont.json = {&quot;foo&quot;:[&quot;a&quot;,&quot;b&quot;,&quot;c&quot;]}</code></pre></div><p>You can also use <code>Json_pointer.at_end</code> to create an append pointer programmatically:</p><div><pre class="language-ocaml"><code># add (any (at_end (of_string_nav &quot;/foo&quot;))) arr_obj ~value:(Jsont.Json.string &quot;c&quot;);; 103 + - : Jsont.json = {&quot;foo&quot;:[&quot;a&quot;,&quot;b&quot;,&quot;c&quot;]}</code></pre></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><pre class="language-ocaml"><code># let items = parse_json {|{&quot;items&quot;:[&quot;x&quot;]}|};; 104 104 val items : Jsont.json = {&quot;items&quot;:[&quot;x&quot;]} 105 105 # add (of_string &quot;/items/0&quot;) items ~value:(Jsont.Json.string &quot;y&quot;);; 106 106 - : Jsont.json = {&quot;items&quot;:[&quot;y&quot;,&quot;x&quot;]} 107 107 # add (of_string &quot;/items/-&quot;) items ~value:(Jsont.Json.string &quot;z&quot;);; 108 - - : Jsont.json = {&quot;items&quot;:[&quot;x&quot;,&quot;z&quot;]}</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 {|{&quot;foo&quot;:&quot;bar&quot;,&quot;baz&quot;:&quot;qux&quot;}|};; 108 + - : Jsont.json = {&quot;items&quot;:[&quot;x&quot;,&quot;z&quot;]}</code></pre></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><pre class="language-ocaml"><code># let two_fields = parse_json {|{&quot;foo&quot;:&quot;bar&quot;,&quot;baz&quot;:&quot;qux&quot;}|};; 109 109 val two_fields : Jsont.json = {&quot;foo&quot;:&quot;bar&quot;,&quot;baz&quot;:&quot;qux&quot;} 110 110 # remove (of_string_nav &quot;/baz&quot;) two_fields ;; 111 - - : Jsont.json = {&quot;foo&quot;:&quot;bar&quot;}</code></pre><p>For arrays, it removes and shifts:</p><pre class="language-ocaml"><code># let three_elem = parse_json {|{&quot;foo&quot;:[&quot;a&quot;,&quot;b&quot;,&quot;c&quot;]}|};; 111 + - : Jsont.json = {&quot;foo&quot;:&quot;bar&quot;}</code></pre></div><p>For arrays, it removes and shifts:</p><div><pre class="language-ocaml"><code># let three_elem = parse_json {|{&quot;foo&quot;:[&quot;a&quot;,&quot;b&quot;,&quot;c&quot;]}|};; 112 112 val three_elem : Jsont.json = {&quot;foo&quot;:[&quot;a&quot;,&quot;b&quot;,&quot;c&quot;]} 113 113 # remove (of_string_nav &quot;/foo/1&quot;) three_elem ;; 114 - - : Jsont.json = {&quot;foo&quot;:[&quot;a&quot;,&quot;c&quot;]}</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 &quot;/foo&quot;) obj ~value:(Jsont.Json.string &quot;baz&quot;) 114 + - : Jsont.json = {&quot;foo&quot;:[&quot;a&quot;,&quot;c&quot;]}</code></pre></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><pre class="language-ocaml"><code># replace (of_string_nav &quot;/foo&quot;) obj ~value:(Jsont.Json.string &quot;baz&quot;) 115 115 ;; 116 - - : Jsont.json = {&quot;foo&quot;:&quot;baz&quot;}</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 {|{&quot;foo&quot;:{&quot;bar&quot;:&quot;baz&quot;},&quot;qux&quot;:{}}|};; 116 + - : Jsont.json = {&quot;foo&quot;:&quot;baz&quot;}</code></pre></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><pre class="language-ocaml"><code># let nested = parse_json {|{&quot;foo&quot;:{&quot;bar&quot;:&quot;baz&quot;},&quot;qux&quot;:{}}|};; 117 117 val nested : Jsont.json = {&quot;foo&quot;:{&quot;bar&quot;:&quot;baz&quot;},&quot;qux&quot;:{}} 118 118 # move ~from:(of_string_nav &quot;/foo/bar&quot;) ~path:(of_string &quot;/qux/thud&quot;) nested;; 119 - - : Jsont.json = {&quot;foo&quot;:{},&quot;qux&quot;:{&quot;thud&quot;:&quot;baz&quot;}}</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 {|{&quot;foo&quot;:{&quot;bar&quot;:&quot;baz&quot;}}|};; 119 + - : Jsont.json = {&quot;foo&quot;:{},&quot;qux&quot;:{&quot;thud&quot;:&quot;baz&quot;}}</code></pre></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><pre class="language-ocaml"><code># let to_copy = parse_json {|{&quot;foo&quot;:{&quot;bar&quot;:&quot;baz&quot;}}|};; 120 120 val to_copy : Jsont.json = {&quot;foo&quot;:{&quot;bar&quot;:&quot;baz&quot;}} 121 121 # copy ~from:(of_string_nav &quot;/foo/bar&quot;) ~path:(of_string &quot;/foo/qux&quot;) to_copy;; 122 - - : Jsont.json = {&quot;foo&quot;:{&quot;bar&quot;:&quot;baz&quot;,&quot;qux&quot;:&quot;baz&quot;}}</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 &quot;/foo&quot;) obj ~expected:(Jsont.Json.string &quot;bar&quot;);; 122 + - : Jsont.json = {&quot;foo&quot;:{&quot;bar&quot;:&quot;baz&quot;,&quot;qux&quot;:&quot;baz&quot;}}</code></pre></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><pre class="language-ocaml"><code># test (of_string_nav &quot;/foo&quot;) obj ~expected:(Jsont.Json.string &quot;bar&quot;);; 123 123 - : bool = true 124 124 # test (of_string_nav &quot;/foo&quot;) obj ~expected:(Jsont.Json.string &quot;wrong&quot;);; 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 &quot;a/b&quot;];; 125 + - : bool = false</code></pre></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><pre class="language-ocaml"><code># let p = make [mem &quot;a/b&quot;];; 126 126 val p : nav t = [Mem &quot;a/b&quot;] 127 127 # to_string p;; 128 128 - : string = &quot;/a~1b&quot; 129 129 # of_string_nav &quot;/a~1b&quot;;; 130 - - : nav t = [Mem &quot;a/b&quot;]</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 &quot;hello&quot;;; 130 + - : nav t = [Mem &quot;a/b&quot;]</code></pre></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><pre class="language-ocaml"><code># Token.escape &quot;hello&quot;;; 131 131 - : string = &quot;hello&quot; 132 132 # Token.escape &quot;a/b&quot;;; 133 133 - : string = &quot;a~1b&quot; 134 134 # Token.escape &quot;a~b&quot;;; 135 135 - : string = &quot;a~0b&quot; 136 136 # Token.escape &quot;~/&quot;;; 137 - - : string = &quot;~0~1&quot;</code></pre><h3 id="unescaping"><a href="#unescaping" class="anchor"></a>Unescaping</h3><p>And the reverse process:</p><pre class="language-ocaml"><code># Token.unescape &quot;a~1b&quot;;; 137 + - : string = &quot;~0~1&quot;</code></pre></div><h3 id="unescaping"><a href="#unescaping" class="anchor"></a>Unescaping</h3><p>And the reverse process:</p><div><pre class="language-ocaml"><code># Token.unescape &quot;a~1b&quot;;; 138 138 - : string = &quot;a/b&quot; 139 139 # Token.unescape &quot;a~0b&quot;;; 140 - - : string = &quot;a~b&quot;</code></pre><h3 id="the-order-matters!"><a href="#the-order-matters!" class="anchor"></a>The Order Matters!</h3><p><a href="https://datatracker.ietf.org/doc/html/rfc6901#section-4">RFC 6901, Section 4</a> is careful to specify the unescaping order:</p><p><i>Evaluation of each reference token begins by decoding any escaped character sequence. This is performed by first transforming any occurrence of the sequence '~1' to '/', and then transforming any occurrence of the sequence '~0' to '~'. By performing the substitutions in this order, an implementation avoids the error of turning '~01' first into '~1' and then into '/', which would be incorrect (the string '~01' correctly becomes '~1' after transformation).</i></p><p>Let's verify this tricky case:</p><pre class="language-ocaml"><code># Token.unescape &quot;~01&quot;;; 141 - - : string = &quot;~1&quot;</code></pre><p>If we unescaped <code>~0</code> first, <code>~01</code> would become <code>~1</code>, which would then become <code>/</code>. But that's wrong! The sequence <code>~01</code> should become the literal string <code>~1</code> (a tilde followed by the digit one).</p><h2 id="uri-fragment-encoding"><a href="#uri-fragment-encoding" class="anchor"></a>URI Fragment Encoding</h2><p>JSON Pointers can be embedded in URIs. <a href="https://datatracker.ietf.org/doc/html/rfc6901#section-6">RFC 6901, Section 6</a> explains:</p><p><i>A JSON Pointer can be represented in a URI fragment identifier by encoding it into octets using UTF-8, while percent-encoding those characters not allowed by the fragment rule in <a href="https://datatracker.ietf.org/doc/html/rfc3986">RFC 3986</a>.</i></p><p>This adds percent-encoding on top of the <code>~0</code>/<code>~1</code> escaping:</p><pre class="language-ocaml"><code># to_uri_fragment (of_string_nav &quot;/foo&quot;);; 140 + - : string = &quot;a~b&quot;</code></pre></div><h3 id="the-order-matters!"><a href="#the-order-matters!" class="anchor"></a>The Order Matters!</h3><p><a href="https://datatracker.ietf.org/doc/html/rfc6901#section-4">RFC 6901, Section 4</a> is careful to specify the unescaping order:</p><p><i>Evaluation of each reference token begins by decoding any escaped character sequence. This is performed by first transforming any occurrence of the sequence '~1' to '/', and then transforming any occurrence of the sequence '~0' to '~'. By performing the substitutions in this order, an implementation avoids the error of turning '~01' first into '~1' and then into '/', which would be incorrect (the string '~01' correctly becomes '~1' after transformation).</i></p><p>Let's verify this tricky case:</p><div><pre class="language-ocaml"><code># Token.unescape &quot;~01&quot;;; 141 + - : string = &quot;~1&quot;</code></pre></div><p>If we unescaped <code>~0</code> first, <code>~01</code> would become <code>~1</code>, which would then become <code>/</code>. But that's wrong! The sequence <code>~01</code> should become the literal string <code>~1</code> (a tilde followed by the digit one).</p><h2 id="uri-fragment-encoding"><a href="#uri-fragment-encoding" class="anchor"></a>URI Fragment Encoding</h2><p>JSON Pointers can be embedded in URIs. <a href="https://datatracker.ietf.org/doc/html/rfc6901#section-6">RFC 6901, Section 6</a> explains:</p><p><i>A JSON Pointer can be represented in a URI fragment identifier by encoding it into octets using UTF-8, while percent-encoding those characters not allowed by the fragment rule in <a href="https://datatracker.ietf.org/doc/html/rfc3986">RFC 3986</a>.</i></p><p>This adds percent-encoding on top of the <code>~0</code>/<code>~1</code> escaping:</p><div><pre class="language-ocaml"><code># to_uri_fragment (of_string_nav &quot;/foo&quot;);; 142 142 - : string = &quot;/foo&quot; 143 143 # to_uri_fragment (of_string_nav &quot;/a~1b&quot;);; 144 144 - : string = &quot;/a~1b&quot; 145 145 # to_uri_fragment (of_string_nav &quot;/c%d&quot;);; 146 146 - : string = &quot;/c%25d&quot; 147 147 # to_uri_fragment (of_string_nav &quot;/ &quot;);; 148 - - : string = &quot;/%20&quot;</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>&quot;&quot;</code> -&gt; <code>#</code> -&gt; whole document</li><li><code>&quot;/foo&quot;</code> -&gt; <code>#/foo</code> -&gt; <code>[&quot;bar&quot;, &quot;baz&quot;]</code></li><li><code>&quot;/foo/0&quot;</code> -&gt; <code>#/foo/0</code> -&gt; <code>&quot;bar&quot;</code></li><li><code>&quot;/&quot;</code> -&gt; <code>#/</code> -&gt; <code>0</code></li><li><code>&quot;/a~1b&quot;</code> -&gt; <code>#/a~1b</code> -&gt; <code>1</code></li><li><code>&quot;/c%d&quot;</code> -&gt; <code>#/c%25d</code> -&gt; <code>2</code></li><li><code>&quot;/ &quot;</code> -&gt; <code>#/%20</code> -&gt; <code>7</code></li><li><code>&quot;/m~0n&quot;</code> -&gt; <code>#/m~0n</code> -&gt; <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 &quot;database&quot;; mem &quot;port&quot;];; 148 + - : string = &quot;/%20&quot;</code></pre></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>&quot;&quot;</code> -&gt; <code>#</code> -&gt; whole document</li><li><code>&quot;/foo&quot;</code> -&gt; <code>#/foo</code> -&gt; <code>[&quot;bar&quot;, &quot;baz&quot;]</code></li><li><code>&quot;/foo/0&quot;</code> -&gt; <code>#/foo/0</code> -&gt; <code>&quot;bar&quot;</code></li><li><code>&quot;/&quot;</code> -&gt; <code>#/</code> -&gt; <code>0</code></li><li><code>&quot;/a~1b&quot;</code> -&gt; <code>#/a~1b</code> -&gt; <code>1</code></li><li><code>&quot;/c%d&quot;</code> -&gt; <code>#/c%25d</code> -&gt; <code>2</code></li><li><code>&quot;/ &quot;</code> -&gt; <code>#/%20</code> -&gt; <code>7</code></li><li><code>&quot;/m~0n&quot;</code> -&gt; <code>#/m~0n</code> -&gt; <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><pre class="language-ocaml"><code># let port_ptr = make [mem &quot;database&quot;; mem &quot;port&quot;];; 149 149 val port_ptr : nav t = [Mem &quot;database&quot;; Mem &quot;port&quot;] 150 150 # to_string port_ptr;; 151 - - : string = &quot;/database/port&quot;</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 &quot;features&quot;; nth 0];; 151 + - : string = &quot;/database/port&quot;</code></pre></div><p>For array access, use the <code>Json_pointer.nth</code> helper:</p><div><pre class="language-ocaml"><code># let first_feature_ptr = make [mem &quot;features&quot;; nth 0];; 152 152 val first_feature_ptr : nav t = [Mem &quot;features&quot;; Nth 0] 153 153 # to_string first_feature_ptr;; 154 - - : string = &quot;/features/0&quot;</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 &quot;/database&quot;;; 154 + - : string = &quot;/features/0&quot;</code></pre></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><pre class="language-ocaml"><code># let db_ptr = of_string_nav &quot;/database&quot;;; 155 155 val db_ptr : nav t = [Mem &quot;database&quot;] 156 156 # let creds_ptr = db_ptr / mem &quot;credentials&quot;;; 157 157 val creds_ptr : nav t = [Mem &quot;database&quot;; Mem &quot;credentials&quot;] 158 158 # let user_ptr = creds_ptr / mem &quot;username&quot;;; 159 159 val user_ptr : nav t = [Mem &quot;database&quot;; Mem &quot;credentials&quot;; Mem &quot;username&quot;] 160 160 # to_string user_ptr;; 161 - - : string = &quot;/database/credentials/username&quot;</code></pre><p>Or concatenate two pointers:</p><pre class="language-ocaml"><code># let base = of_string_nav &quot;/api/v1&quot;;; 161 + - : string = &quot;/database/credentials/username&quot;</code></pre></div><p>Or concatenate two pointers:</p><div><pre class="language-ocaml"><code># let base = of_string_nav &quot;/api/v1&quot;;; 162 162 val base : nav t = [Mem &quot;api&quot;; Mem &quot;v1&quot;] 163 163 # let endpoint = of_string_nav &quot;/users/0&quot;;; 164 164 val endpoint : nav t = [Mem &quot;users&quot;; Nth 0] 165 165 # to_string (concat base endpoint);; 166 - - : string = &quot;/api/v1/users/0&quot;</code></pre><h2 id="jsont-integration"><a href="#jsont-integration" class="anchor"></a>Jsont Integration</h2><p>The library integrates with the <code>Jsont</code> codec system, allowing you to combine JSON Pointer navigation with typed decoding. This is powerful because you can point to a location in a JSON document and decode it directly to an OCaml type.</p><pre class="language-ocaml"><code># let config_json = parse_json {|{ 166 + - : string = &quot;/api/v1/users/0&quot;</code></pre></div><h2 id="jsont-integration"><a href="#jsont-integration" class="anchor"></a>Jsont Integration</h2><p>The library integrates with the <code>Jsont</code> codec system, allowing you to combine JSON Pointer navigation with typed decoding. This is powerful because you can point to a location in a JSON document and decode it directly to an OCaml type.</p><div><pre class="language-ocaml"><code># let config_json = parse_json {|{ 167 167 &quot;database&quot;: { 168 168 &quot;host&quot;: &quot;localhost&quot;, 169 169 &quot;port&quot;: 5432, ··· 172 172 &quot;features&quot;: [&quot;auth&quot;, &quot;logging&quot;, &quot;metrics&quot;] 173 173 }|};; 174 174 val config_json : Jsont.json = 175 - {&quot;database&quot;:{&quot;host&quot;:&quot;localhost&quot;,&quot;port&quot;:5432,&quot;credentials&quot;:{&quot;username&quot;:&quot;admin&quot;,&quot;password&quot;:&quot;secret&quot;}},&quot;features&quot;:[&quot;auth&quot;,&quot;logging&quot;,&quot;metrics&quot;]}</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 &quot;/database/host&quot;;; 175 + {&quot;database&quot;:{&quot;host&quot;:&quot;localhost&quot;,&quot;port&quot;:5432,&quot;credentials&quot;:{&quot;username&quot;:&quot;admin&quot;,&quot;password&quot;:&quot;secret&quot;}},&quot;features&quot;:[&quot;auth&quot;,&quot;logging&quot;,&quot;metrics&quot;]}</code></pre></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><pre class="language-ocaml"><code># let nav = of_string_nav &quot;/database/host&quot;;; 176 176 val nav : nav t = [Mem &quot;database&quot;; Mem &quot;host&quot;] 177 177 # let db_host = 178 178 Jsont.Json.decode ··· 185 185 (path (of_string_nav &quot;/database/port&quot;) Jsont.int) 186 186 config_json 187 187 |&gt; Result.get_ok;; 188 - val db_port : int = 5432</code></pre><p>Extract a list of strings:</p><pre class="language-ocaml"><code># let features = 188 + val db_port : int = 5432</code></pre></div><p>Extract a list of strings:</p><div><pre class="language-ocaml"><code># let features = 189 189 Jsont.Json.decode 190 190 (path (of_string_nav &quot;/features&quot;) Jsont.(list string)) 191 191 config_json 192 192 |&gt; Result.get_ok;; 193 - val features : string list = [&quot;auth&quot;; &quot;logging&quot;; &quot;metrics&quot;]</code></pre><h3 id="default-values-with-~absent"><a href="#default-values-with-~absent" class="anchor"></a>Default Values with <code>~absent</code></h3><p>Use <code>~absent</code> to provide a default when a path doesn't exist:</p><pre class="language-ocaml"><code># let timeout = 193 + val features : string list = [&quot;auth&quot;; &quot;logging&quot;; &quot;metrics&quot;]</code></pre></div><h3 id="default-values-with-~absent"><a href="#default-values-with-~absent" class="anchor"></a>Default Values with <code>~absent</code></h3><p>Use <code>~absent</code> to provide a default when a path doesn't exist:</p><div><pre class="language-ocaml"><code># let timeout = 194 194 Jsont.Json.decode 195 195 (path ~absent:30 (of_string_nav &quot;/database/timeout&quot;) Jsont.int) 196 196 config_json 197 197 |&gt; Result.get_ok;; 198 - val timeout : int = 30</code></pre><h3 id="nested-path-extraction"><a href="#nested-path-extraction" class="anchor"></a>Nested Path Extraction</h3><p>You can extract values from deeply nested structures:</p><pre class="language-ocaml"><code># let org_json = parse_json {|{ 198 + val timeout : int = 30</code></pre></div><h3 id="nested-path-extraction"><a href="#nested-path-extraction" class="anchor"></a>Nested Path Extraction</h3><p>You can extract values from deeply nested structures:</p><div><pre class="language-ocaml"><code># let org_json = parse_json {|{ 199 199 &quot;organization&quot;: { 200 200 &quot;owner&quot;: {&quot;name&quot;: &quot;Alice&quot;, &quot;email&quot;: &quot;alice@example.com&quot;, &quot;age&quot;: 35}, 201 201 &quot;members&quot;: [{&quot;name&quot;: &quot;Bob&quot;, &quot;email&quot;: &quot;bob@example.com&quot;, &quot;age&quot;: 28}] ··· 212 212 (path (of_string_nav &quot;/organization/members/0/age&quot;) Jsont.int) 213 213 org_json 214 214 |&gt; Result.get_ok;; 215 - - : int = 28</code></pre><h3 id="comparison:-raw-vs-typed-access"><a href="#comparison:-raw-vs-typed-access" class="anchor"></a>Comparison: Raw vs Typed Access</h3><p><b>Raw access</b> requires pattern matching:</p><pre class="language-ocaml"><code># let raw_port = 215 + - : int = 28</code></pre></div><h3 id="comparison:-raw-vs-typed-access"><a href="#comparison:-raw-vs-typed-access" class="anchor"></a>Comparison: Raw vs Typed Access</h3><p><b>Raw access</b> requires pattern matching:</p><div><pre class="language-ocaml"><code># let raw_port = 216 216 match get (of_string_nav &quot;/database/port&quot;) config_json with 217 217 | Jsont.Number (f, _) -&gt; int_of_float f 218 218 | _ -&gt; failwith &quot;expected number&quot;;; 219 - val raw_port : int = 5432</code></pre><p><b>Typed access</b> is cleaner and type-safe:</p><pre class="language-ocaml"><code># let typed_port = 219 + val raw_port : int = 5432</code></pre></div><p><b>Typed access</b> is cleaner and type-safe:</p><div><pre class="language-ocaml"><code># let typed_port = 220 220 Jsont.Json.decode 221 221 (path (of_string_nav &quot;/database/port&quot;) Jsont.int) 222 222 config_json 223 223 |&gt; 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>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 {|{&quot;tasks&quot;:[&quot;buy milk&quot;]}|};; 224 + val typed_port : int = 5432</code></pre></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><pre class="language-ocaml"><code># let tasks = parse_json {|{&quot;tasks&quot;:[&quot;buy milk&quot;]}|};; 225 225 val tasks : Jsont.json = {&quot;tasks&quot;:[&quot;buy milk&quot;]} 226 226 # set (of_string &quot;/tasks/0&quot;) tasks ~value:(Jsont.Json.string &quot;buy eggs&quot;);; 227 227 - : Jsont.json = {&quot;tasks&quot;:[&quot;buy eggs&quot;]} 228 228 # set (of_string &quot;/tasks/-&quot;) tasks ~value:(Jsont.Json.string &quot;call mom&quot;);; 229 - - : Jsont.json = {&quot;tasks&quot;:[&quot;buy milk&quot;,&quot;call mom&quot;]}</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>&quot;add&quot;</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 &quot;/tasks/0&quot;;; 229 + - : Jsont.json = {&quot;tasks&quot;:[&quot;buy milk&quot;,&quot;call mom&quot;]}</code></pre></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>&quot;add&quot;</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><pre class="language-ocaml"><code># of_string_kind &quot;/tasks/0&quot;;; 230 230 - : [ `Append of append t | `Nav of nav t ] = `Nav [Mem &quot;tasks&quot;; Nth 0] 231 231 # of_string_kind &quot;/tasks/-&quot;;; 232 - - : [ `Append of append t | `Nav of nav t ] = `Append [Mem &quot;tasks&quot;] /-</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 &quot;append position&quot; 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> 232 + - : [ `Append of append t | `Nav of nav t ] = `Append [Mem &quot;tasks&quot;] /-</code></pre></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 &quot;append position&quot; 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>