RFC6901 JSON Pointer implementation in OCaml using jsont

pp

+1 -1
doc/dune
··· 1 (mdx 2 - (libraries jsont jsont.bytesrw jsont_pointer))
··· 1 (mdx 2 + (libraries jsont jsont.bytesrw jsont_pointer jsont_pointer_top))
+28 -44
doc/tutorial.md
··· 10 11 ```ocaml 12 # open Jsont_pointer;; 13 # let parse_json s = 14 match Jsont_bytesrw.decode_string Jsont.json s with 15 | Ok json -> json ··· 20 | Ok s -> s 21 | Error e -> failwith e;; 22 val json_to_string : Jsont.json -> string = <fun> 23 - # let show_pointer p = 24 - let idxs = indices p in 25 - let show_index = function 26 - | `Mem s -> "Mem:" ^ s 27 - | `Nth n -> "Nth:" ^ string_of_int n 28 - | `End -> "End" 29 - in 30 - "[" ^ String.concat ", " (List.map show_index idxs) ^ "]";; 31 - val show_pointer : t -> string = <fun> 32 ``` 33 34 ## What is JSON Pointer? ··· 71 72 ```ocaml 73 # let ptr = of_string "/users/0/name";; 74 - val ptr : t = <abstr> 75 # get ptr users_json;; 76 - : Jsont.json = Jsont.String ("Alice", <abstr>) 77 ``` ··· 101 Let's see this in action: 102 103 ```ocaml 104 - # let empty_ptr = of_string "";; 105 - val empty_ptr : t = <abstr> 106 - # show_pointer empty_ptr;; 107 - - : string = "[]" 108 ``` 109 110 The empty pointer has no reference tokens - it points to the root. 111 112 ```ocaml 113 - # let foo_ptr = of_string "/foo";; 114 - val foo_ptr : t = <abstr> 115 - # show_pointer foo_ptr;; 116 - - : string = "[Mem:foo]" 117 ``` 118 119 The pointer `/foo` has one token: `foo`. Since it's not a number, it's 120 interpreted as an object member name (`Mem`). 121 122 ```ocaml 123 - # let foo_0_ptr = of_string "/foo/0";; 124 - val foo_0_ptr : t = <abstr> 125 - # show_pointer foo_0_ptr;; 126 - - : string = "[Mem:foo, Nth:0]" 127 ``` 128 129 Here we have two tokens: `foo` (a member name) and `0` (interpreted as 130 an array index `Nth`). 131 132 ```ocaml 133 - # show_pointer (of_string "/foo/bar/baz");; 134 - - : string = "[Mem:foo, Mem:bar, Mem:baz]" 135 ``` 136 137 Multiple tokens navigate deeper into nested structures. ··· 171 - : (t, string) result = 172 Error "Invalid JSON Pointer: must be empty or start with '/': foo" 173 # of_string_result "/valid";; 174 - - : (t, string) result = Ok <abstr> 175 ``` 176 177 ## Evaluation: Navigating JSON ··· 285 286 ```ocaml 287 # let slash_ptr = make [`Mem "a/b"];; 288 - val slash_ptr : t = <abstr> 289 # to_string slash_ptr;; 290 - : string = "/a~1b" 291 # get slash_ptr rfc_example |> json_to_string;; ··· 358 > note that leading zeros are not allowed 359 360 ```ocaml 361 - # show_pointer (of_string "/foo/0");; 362 - - : string = "[Mem:foo, Nth:0]" 363 ``` 364 365 Zero itself is fine. 366 367 ```ocaml 368 - # show_pointer (of_string "/foo/01");; 369 - - : string = "[Mem:foo, Mem:01]" 370 ``` 371 372 But `01` has a leading zero, so it's NOT treated as an array index - it ··· 384 how it parses: 385 386 ```ocaml 387 - # show_pointer (of_string "/foo/-");; 388 - - : string = "[Mem:foo, End]" 389 ``` 390 391 The `-` is recognized as a special `End` index. ··· 562 563 ```ocaml 564 # let p = make [`Mem "a/b"];; 565 - val p : t = <abstr> 566 # to_string p;; 567 - : string = "/a~1b" 568 - # let p' = of_string "/a~1b";; 569 - val p' : t = <abstr> 570 - # show_pointer p';; 571 - - : string = "[Mem:a/b]" 572 ``` 573 574 ### Escaping in Action ··· 663 664 ```ocaml 665 # let port_ptr = make [`Mem "database"; `Mem "port"];; 666 - val port_ptr : t = <abstr> 667 # to_string port_ptr;; 668 - : string = "/database/port" 669 ``` ··· 672 673 ```ocaml 674 # let first_feature_ptr = make [`Mem "features"; `Nth 0];; 675 - val first_feature_ptr : t = <abstr> 676 # to_string first_feature_ptr;; 677 - : string = "/features/0" 678 ``` ··· 683 684 ```ocaml 685 # let db_ptr = of_string "/database";; 686 - val db_ptr : t = <abstr> 687 # let creds_ptr = append db_ptr (`Mem "credentials");; 688 - val creds_ptr : t = <abstr> 689 # let user_ptr = append creds_ptr (`Mem "username");; 690 - val user_ptr : t = <abstr> 691 # to_string user_ptr;; 692 - : string = "/database/credentials/username" 693 ``` ··· 696 697 ```ocaml 698 # let base = of_string "/api/v1";; 699 - val base : t = <abstr> 700 # let endpoint = of_string "/users/0";; 701 - val endpoint : t = <abstr> 702 # to_string (concat base endpoint);; 703 - : string = "/api/v1/users/0" 704 ```
··· 10 11 ```ocaml 12 # open Jsont_pointer;; 13 + # #install_printer Jsont_pointer_top.printer;; 14 # let parse_json s = 15 match Jsont_bytesrw.decode_string Jsont.json s with 16 | Ok json -> json ··· 21 | Ok s -> s 22 | Error e -> failwith e;; 23 val json_to_string : Jsont.json -> string = <fun> 24 ``` 25 26 ## What is JSON Pointer? ··· 63 64 ```ocaml 65 # let ptr = of_string "/users/0/name";; 66 + val ptr : t = [`Mem "users"; `Nth 0; `Mem "name"] 67 # get ptr users_json;; 68 - : Jsont.json = Jsont.String ("Alice", <abstr>) 69 ``` ··· 93 Let's see this in action: 94 95 ```ocaml 96 + # of_string "";; 97 + - : t = [] 98 ``` 99 100 The empty pointer has no reference tokens - it points to the root. 101 102 ```ocaml 103 + # of_string "/foo";; 104 + - : t = [`Mem "foo"] 105 ``` 106 107 The pointer `/foo` has one token: `foo`. Since it's not a number, it's 108 interpreted as an object member name (`Mem`). 109 110 ```ocaml 111 + # of_string "/foo/0";; 112 + - : t = [`Mem "foo"; `Nth 0] 113 ``` 114 115 Here we have two tokens: `foo` (a member name) and `0` (interpreted as 116 an array index `Nth`). 117 118 ```ocaml 119 + # of_string "/foo/bar/baz";; 120 + - : t = [`Mem "foo"; `Mem "bar"; `Mem "baz"] 121 ``` 122 123 Multiple tokens navigate deeper into nested structures. ··· 157 - : (t, string) result = 158 Error "Invalid JSON Pointer: must be empty or start with '/': foo" 159 # of_string_result "/valid";; 160 + - : (t, string) result = Ok [`Mem "valid"] 161 ``` 162 163 ## Evaluation: Navigating JSON ··· 271 272 ```ocaml 273 # let slash_ptr = make [`Mem "a/b"];; 274 + val slash_ptr : t = [`Mem "a/b"] 275 # to_string slash_ptr;; 276 - : string = "/a~1b" 277 # get slash_ptr rfc_example |> json_to_string;; ··· 344 > note that leading zeros are not allowed 345 346 ```ocaml 347 + # of_string "/foo/0";; 348 + - : t = [`Mem "foo"; `Nth 0] 349 ``` 350 351 Zero itself is fine. 352 353 ```ocaml 354 + # of_string "/foo/01";; 355 + - : t = [`Mem "foo"; `Mem "01"] 356 ``` 357 358 But `01` has a leading zero, so it's NOT treated as an array index - it ··· 370 how it parses: 371 372 ```ocaml 373 + # of_string "/foo/-";; 374 + - : t = [`Mem "foo"; `End] 375 ``` 376 377 The `-` is recognized as a special `End` index. ··· 548 549 ```ocaml 550 # let p = make [`Mem "a/b"];; 551 + val p : t = [`Mem "a/b"] 552 # to_string p;; 553 - : string = "/a~1b" 554 + # of_string "/a~1b";; 555 + - : t = [`Mem "a/b"] 556 ``` 557 558 ### Escaping in Action ··· 647 648 ```ocaml 649 # let port_ptr = make [`Mem "database"; `Mem "port"];; 650 + val port_ptr : t = [`Mem "database"; `Mem "port"] 651 # to_string port_ptr;; 652 - : string = "/database/port" 653 ``` ··· 656 657 ```ocaml 658 # let first_feature_ptr = make [`Mem "features"; `Nth 0];; 659 + val first_feature_ptr : t = [`Mem "features"; `Nth 0] 660 # to_string first_feature_ptr;; 661 - : string = "/features/0" 662 ``` ··· 667 668 ```ocaml 669 # let db_ptr = of_string "/database";; 670 + val db_ptr : t = [`Mem "database"] 671 # let creds_ptr = append db_ptr (`Mem "credentials");; 672 + val creds_ptr : t = [`Mem "database"; `Mem "credentials"] 673 # let user_ptr = append creds_ptr (`Mem "username");; 674 + val user_ptr : t = [`Mem "database"; `Mem "credentials"; `Mem "username"] 675 # to_string user_ptr;; 676 - : string = "/database/credentials/username" 677 ``` ··· 680 681 ```ocaml 682 # let base = of_string "/api/v1";; 683 + val base : t = [`Mem "api"; `Mem "v1"] 684 # let endpoint = of_string "/users/0";; 685 + val endpoint : t = [`Mem "users"; `Nth 0] 686 # to_string (concat base endpoint);; 687 - : string = "/api/v1/users/0" 688 ```
+10
src/jsont_pointer.ml
··· 253 let pp ppf p = 254 Format.pp_print_string ppf (to_string p) 255 256 (* Comparison *) 257 258 let segment_equal s1 s2 = match s1, s2 with
··· 253 let pp ppf p = 254 Format.pp_print_string ppf (to_string p) 255 256 + let pp_verbose ppf p = 257 + let pp_index ppf = function 258 + | `Mem s -> Format.fprintf ppf "`Mem %S" s 259 + | `Nth n -> Format.fprintf ppf "`Nth %d" n 260 + | `End -> Format.fprintf ppf "`End" 261 + in 262 + Format.fprintf ppf "[%a]" 263 + (Format.pp_print_list ~pp_sep:(fun ppf () -> Format.fprintf ppf "; ") pp_index) 264 + (indices p) 265 + 266 (* Comparison *) 267 268 let segment_equal s1 s2 = match s1, s2 with
+5
src/jsont_pointer.mli
··· 189 val pp : Format.formatter -> t -> unit 190 (** [pp] formats a pointer using {!to_string}. *) 191 192 (** {2:comparison Comparison} *) 193 194 val equal : t -> t -> bool
··· 189 val pp : Format.formatter -> t -> unit 190 (** [pp] formats a pointer using {!to_string}. *) 191 192 + val pp_verbose : Format.formatter -> t -> unit 193 + (** [pp_verbose] formats a pointer showing its index structure. 194 + For example, [/foo/0/-] is formatted as [[`Mem "foo"; `Nth 0; `End]]. 195 + Useful for debugging and understanding pointer structure. *) 196 + 197 (** {2:comparison Comparison} *) 198 199 val equal : t -> t -> bool