···28282929The JSON Pointer `/users/0/name` refers to the string `"Alice"`.
30303131+In OCaml, this is represented by the `Jsont_pointer.t` type - a sequence
3232+of navigation steps from the document root to a target value.
3333+3134## Syntax: Reference Tokens
32353336RFC 6901, Section 3 defines the syntax:
···79828083Multiple tokens navigate deeper into nested structures.
81848282-### Invalid Syntax
8383-8484-What happens if a pointer doesn't start with `/`?
8585-8686-```sh
8787-$ jsonpp parse "foo"
8888-ERROR: Invalid JSON Pointer: must be empty or start with '/': foo
8989-```
9090-9191-The RFC is strict: non-empty pointers MUST start with `/`.
9292-9393-## Escaping Special Characters
9494-9595-RFC 6901, Section 3 explains the escaping rules:
9696-9797-> Because the characters '~' (%x7E) and '/' (%x2F) have special meanings
9898-> in JSON Pointer, '~' needs to be encoded as '~0' and '/' needs to be
9999-> encoded as '~1' when these characters appear in a reference token.
100100-101101-Why these specific characters?
102102-- `/` separates tokens, so it must be escaped inside a token
103103-- `~` is the escape character itself, so it must also be escaped
104104-105105-The escape sequences are:
106106-- `~0` represents `~` (tilde)
107107-- `~1` represents `/` (forward slash)
108108-109109-Let's see escaping in action:
110110-111111-```sh
112112-$ jsonpp escape "hello"
113113-hello
114114-```
115115-116116-No special characters, no escaping needed.
117117-118118-```sh
119119-$ jsonpp escape "a/b"
120120-a~1b
121121-```
122122-123123-The `/` becomes `~1`.
124124-125125-```sh
126126-$ jsonpp escape "a~b"
127127-a~0b
128128-```
129129-130130-The `~` becomes `~0`.
8585+### The Index Type
13186132132-```sh
133133-$ jsonpp escape "~/"
134134-~0~1
135135-```
136136-137137-Both characters are escaped.
138138-139139-### Unescaping
140140-141141-And the reverse process:
142142-143143-```sh
144144-$ jsonpp unescape "a~1b"
145145-OK: a/b
146146-```
8787+Each reference token becomes an `Index.t` value in the library:
14788148148-```sh
149149-$ jsonpp unescape "a~0b"
150150-OK: a~b
8989+```ocaml
9090+type t =
9191+ | Mem of string (* Object member access *)
9292+ | Nth of int (* Array index access *)
9393+ | End (* The special "-" marker for append operations *)
15194```
15295153153-### The Order Matters!
9696+The `Mem` variant holds the **unescaped** member name - you work with the
9797+actual key string (like `"a/b"`) and the library handles any escaping needed
9898+for the JSON Pointer string representation.
15499155155-RFC 6901, Section 4 is careful to specify the unescaping order:
156156-157157-> Evaluation of each reference token begins by decoding any escaped
158158-> character sequence. This is performed by first transforming any
159159-> occurrence of the sequence '~1' to '/', and then transforming any
160160-> occurrence of the sequence '~0' to '~'. By performing the substitutions
161161-> in this order, an implementation avoids the error of turning '~01' first
162162-> into '~1' and then into '/', which would be incorrect (the string '~01'
163163-> correctly becomes '~1' after transformation).
100100+### Invalid Syntax
164101165165-Let's verify this tricky case:
102102+What happens if a pointer doesn't start with `/`?
166103167104```sh
168168-$ jsonpp unescape "~01"
169169-OK: ~1
105105+$ jsonpp parse "foo"
106106+ERROR: Invalid JSON Pointer: must be empty or start with '/': foo
170107```
171108172172-If we unescaped `~0` first, `~01` would become `~1`, which would then become
173173-`/`. But that's wrong! The sequence `~01` should become the literal string
174174-`~1` (a tilde followed by the digit one).
175175-176176-Invalid escape sequences are rejected:
177177-178178-```sh
179179-$ jsonpp unescape "~2"
180180-ERROR: Invalid JSON Pointer: invalid escape sequence ~2
181181-```
182182-183183-```sh
184184-$ jsonpp unescape "hello~"
185185-ERROR: Invalid JSON Pointer: incomplete escape sequence at end
186186-```
109109+The RFC is strict: non-empty pointers MUST start with `/`.
187110188111## Evaluation: Navigating JSON
189112···195118> the document. Each reference token in the JSON Pointer is evaluated
196119> sequentially.
197120121121+In the library, this is the `Jsont_pointer.get` function:
122122+123123+```ocaml
124124+val get : t -> Jsont.json -> Jsont.json
125125+```
126126+198127Let's use the example JSON document from RFC 6901, Section 5:
199128200129```sh
···222151OK: {"foo":["bar","baz"],"":0,"a/b":1,"c%d":2,"e^f":3,"g|h":4,"i\\j":5,"k\"l":6," ":7,"m~n":8}
223152```
224153225225-The empty pointer returns the whole document.
154154+The empty pointer returns the whole document. In OCaml, this is
155155+`Jsont_pointer.root`:
156156+157157+```ocaml
158158+val root : t
159159+(** The empty pointer that references the whole document. *)
160160+```
226161227162### Object Member Access
228163···263198264199### Keys with Special Characters
265200266266-Now for the escape sequences:
201201+The RFC example includes keys with `/` and `~` characters:
267202268203```sh
269204$ jsonpp eval rfc6901_example.json "/a~1b"
270205OK: 1
271206```
272207273273-The token `a~1b` unescapes to `a/b`, which is the key name.
208208+The token `a~1b` refers to the key `a/b`. We'll explain this escaping
209209+[below](#escaping-special-characters).
274210275211```sh
276212$ jsonpp eval rfc6901_example.json "/m~0n"
277213OK: 8
278214```
279215280280-The token `m~0n` unescapes to `m~n`.
216216+The token `m~0n` refers to the key `m~n`.
217217+218218+**Important**: When using the OCaml library programmatically, you don't need
219219+to worry about escaping. The `Index.Mem` variant holds the literal key name:
220220+221221+```ocaml
222222+(* To access the key "a/b", just use the literal string *)
223223+let pointer = Jsont_pointer.make [Mem "a/b"]
224224+225225+(* The library escapes it when converting to string *)
226226+let s = Jsont_pointer.to_string pointer (* "/a~1b" *)
227227+```
281228282229### Other Special Characters (No Escaping Needed)
283230···329276$ jsonpp eval rfc6901_example.json "/foo/0/invalid"
330277ERROR: JSON Pointer: cannot index into string with 'invalid'
331278File "-":
279279+```
280280+281281+The library provides both exception-raising and result-returning variants:
282282+283283+```ocaml
284284+val get : t -> Jsont.json -> Jsont.json
285285+val get_result : t -> Jsont.json -> (Jsont.json, Jsont.Error.t) result
286286+val find : t -> Jsont.json -> Jsont.json option
332287```
333288334289### Array Index Rules
···393348394349But we'll see later that `-` is very useful for mutation operations!
395350396396-## URI Fragment Encoding
397397-398398-JSON Pointers can be embedded in URIs. RFC 6901, Section 6 explains:
399399-400400-> A JSON Pointer can be represented in a URI fragment identifier by
401401-> encoding it into octets using UTF-8, while percent-encoding those
402402-> characters not allowed by the fragment rule in RFC 3986.
403403-404404-This adds percent-encoding on top of the `~0`/`~1` escaping:
405405-406406-```sh
407407-$ jsonpp uri-fragment "/foo"
408408-OK: /foo -> /foo
409409-```
410410-411411-Simple pointers often don't need percent-encoding.
412412-413413-```sh
414414-$ jsonpp uri-fragment "/a~1b"
415415-OK: /a~1b -> /a~1b
416416-```
417417-418418-The `~1` escape stays as-is (it's valid in URI fragments).
419419-420420-```sh
421421-$ jsonpp uri-fragment "/c%d"
422422-OK: /c%d -> /c%25d
423423-```
424424-425425-The `%` character must be percent-encoded as `%25` in URIs!
426426-427427-```sh
428428-$ jsonpp uri-fragment "/ "
429429-OK: / -> /%20
430430-```
431431-432432-Spaces become `%20`.
433433-434434-Here's the RFC example showing the URI fragment forms:
435435-436436-| JSON Pointer | URI Fragment | Value |
437437-|-------------|-------------|-------|
438438-| `""` | `#` | whole document |
439439-| `"/foo"` | `#/foo` | `["bar", "baz"]` |
440440-| `"/foo/0"` | `#/foo/0` | `"bar"` |
441441-| `"/"` | `#/` | `0` |
442442-| `"/a~1b"` | `#/a~1b` | `1` |
443443-| `"/c%d"` | `#/c%25d` | `2` |
444444-| `"/ "` | `#/%20` | `7` |
445445-| `"/m~0n"` | `#/m~0n` | `8` |
446446-447351## Mutation Operations
448352449353While RFC 6901 defines JSON Pointer for read-only access, RFC 6902
···459363{"foo":"bar","baz":"qux"}
460364```
461365366366+In OCaml:
367367+368368+```ocaml
369369+val add : t -> Jsont.json -> value:Jsont.json -> Jsont.json
370370+```
371371+462372For arrays, `add` inserts BEFORE the specified index:
463373464374```sh
···538448false
539449```
540450451451+## Escaping Special Characters
452452+453453+RFC 6901, Section 3 explains the escaping rules:
454454+455455+> Because the characters '\~' (%x7E) and '/' (%x2F) have special meanings
456456+> in JSON Pointer, '\~' needs to be encoded as '\~0' and '/' needs to be
457457+> encoded as '\~1' when these characters appear in a reference token.
458458+459459+Why these specific characters?
460460+- `/` separates tokens, so it must be escaped inside a token
461461+- `~` is the escape character itself, so it must also be escaped
462462+463463+The escape sequences are:
464464+- `~0` represents `~` (tilde)
465465+- `~1` represents `/` (forward slash)
466466+467467+### The Library Handles Escaping Automatically
468468+469469+**Important**: When using `jsont-pointer` programmatically, you rarely need
470470+to think about escaping. The `Index.Mem` variant stores unescaped strings,
471471+and escaping happens automatically during serialization:
472472+473473+```ocaml
474474+(* Create a pointer to key "a/b" - no escaping needed *)
475475+let p = Jsont_pointer.make [Mem "a/b"]
476476+477477+(* Serialize to string - escaping happens automatically *)
478478+let s = Jsont_pointer.to_string p (* Returns "/a~1b" *)
479479+480480+(* Parse from string - unescaping happens automatically *)
481481+let p' = Jsont_pointer.of_string "/a~1b"
482482+(* p' contains [Mem "a/b"] - the unescaped key *)
483483+```
484484+485485+The `Token` module exposes the escaping functions if you need them:
486486+487487+```ocaml
488488+module Token : sig
489489+ val escape : string -> string (* "a/b" -> "a~1b" *)
490490+ val unescape : string -> string (* "a~1b" -> "a/b" *)
491491+end
492492+```
493493+494494+### Escaping in Action
495495+496496+Let's see escaping with the CLI tool:
497497+498498+```sh
499499+$ jsonpp escape "hello"
500500+hello
501501+```
502502+503503+No special characters, no escaping needed.
504504+505505+```sh
506506+$ jsonpp escape "a/b"
507507+a~1b
508508+```
509509+510510+The `/` becomes `~1`.
511511+512512+```sh
513513+$ jsonpp escape "a~b"
514514+a~0b
515515+```
516516+517517+The `~` becomes `~0`.
518518+519519+```sh
520520+$ jsonpp escape "~/"
521521+~0~1
522522+```
523523+524524+Both characters are escaped.
525525+526526+### Unescaping
527527+528528+And the reverse process:
529529+530530+```sh
531531+$ jsonpp unescape "a~1b"
532532+OK: a/b
533533+```
534534+535535+```sh
536536+$ jsonpp unescape "a~0b"
537537+OK: a~b
538538+```
539539+540540+### The Order Matters!
541541+542542+RFC 6901, Section 4 is careful to specify the unescaping order:
543543+544544+> Evaluation of each reference token begins by decoding any escaped
545545+> character sequence. This is performed by first transforming any
546546+> occurrence of the sequence '~1' to '/', and then transforming any
547547+> occurrence of the sequence '~0' to '~'. By performing the substitutions
548548+> in this order, an implementation avoids the error of turning '~01' first
549549+> into '~1' and then into '/', which would be incorrect (the string '~01'
550550+> correctly becomes '~1' after transformation).
551551+552552+Let's verify this tricky case:
553553+554554+```sh
555555+$ jsonpp unescape "~01"
556556+OK: ~1
557557+```
558558+559559+If we unescaped `~0` first, `~01` would become `~1`, which would then become
560560+`/`. But that's wrong! The sequence `~01` should become the literal string
561561+`~1` (a tilde followed by the digit one).
562562+563563+Invalid escape sequences are rejected:
564564+565565+```sh
566566+$ jsonpp unescape "~2"
567567+ERROR: Invalid JSON Pointer: invalid escape sequence ~2
568568+```
569569+570570+```sh
571571+$ jsonpp unescape "hello~"
572572+ERROR: Invalid JSON Pointer: incomplete escape sequence at end
573573+```
574574+575575+## URI Fragment Encoding
576576+577577+JSON Pointers can be embedded in URIs. RFC 6901, Section 6 explains:
578578+579579+> A JSON Pointer can be represented in a URI fragment identifier by
580580+> encoding it into octets using UTF-8, while percent-encoding those
581581+> characters not allowed by the fragment rule in RFC 3986.
582582+583583+This adds percent-encoding on top of the `~0`/`~1` escaping:
584584+585585+```sh
586586+$ jsonpp uri-fragment "/foo"
587587+OK: /foo -> /foo
588588+```
589589+590590+Simple pointers often don't need percent-encoding.
591591+592592+```sh
593593+$ jsonpp uri-fragment "/a~1b"
594594+OK: /a~1b -> /a~1b
595595+```
596596+597597+The `~1` escape stays as-is (it's valid in URI fragments).
598598+599599+```sh
600600+$ jsonpp uri-fragment "/c%d"
601601+OK: /c%d -> /c%25d
602602+```
603603+604604+The `%` character must be percent-encoded as `%25` in URIs!
605605+606606+```sh
607607+$ jsonpp uri-fragment "/ "
608608+OK: / -> /%20
609609+```
610610+611611+Spaces become `%20`.
612612+613613+The library provides functions for URI fragment encoding:
614614+615615+```ocaml
616616+val to_uri_fragment : t -> string
617617+val of_uri_fragment : string -> t
618618+val jsont_uri_fragment : t Jsont.t
619619+```
620620+621621+Here's the RFC example showing the URI fragment forms:
622622+623623+| JSON Pointer | URI Fragment | Value |
624624+|-------------|-------------|-------|
625625+| `""` | `#` | whole document |
626626+| `"/foo"` | `#/foo` | `["bar", "baz"]` |
627627+| `"/foo/0"` | `#/foo/0` | `"bar"` |
628628+| `"/"` | `#/` | `0` |
629629+| `"/a~1b"` | `#/a~1b` | `1` |
630630+| `"/c%d"` | `#/c%25d` | `2` |
631631+| `"/ "` | `#/%20` | `7` |
632632+| `"/m~0n"` | `#/m~0n` | `8` |
633633+541634## Deeply Nested Structures
542635543636JSON Pointer handles arbitrarily deep nesting:
···561654{"arr":[[1,99,2],[3,4]]}
562655```
563656657657+## Jsont Integration
658658+659659+The library integrates with the `Jsont` codec system for typed access:
660660+661661+```ocaml
662662+(* Codec for JSON Pointers as JSON strings *)
663663+val jsont : t Jsont.t
664664+665665+(* Query combinators *)
666666+val path : ?absent:'a -> t -> 'a Jsont.t -> 'a Jsont.t
667667+val set_path : ?allow_absent:bool -> 'a Jsont.t -> t -> 'a -> Jsont.json Jsont.t
668668+val update_path : ?absent:'a -> t -> 'a Jsont.t -> Jsont.json Jsont.t
669669+val delete_path : ?allow_absent:bool -> t -> Jsont.json Jsont.t
670670+```
671671+672672+These allow you to use JSON Pointers with typed codecs rather than raw
673673+`Jsont.json` values.
674674+564675## Summary
565676566677JSON Pointer (RFC 6901) provides a simple but powerful way to address
567678values within JSON documents:
5686795696801. **Syntax**: Pointers are strings of `/`-separated reference tokens
570570-2. **Escaping**: Use `~0` for `~` and `~1` for `/` in tokens
681681+2. **Escaping**: Use `~0` for `~` and `~1` for `/` in tokens (handled automatically by the library)
5716823. **Evaluation**: Tokens navigate through objects (by key) and arrays (by index)
5726834. **URI Encoding**: Pointers can be percent-encoded for use in URIs
5736845. **Mutations**: Combined with JSON Patch (RFC 6902), pointers enable structured updates