RFC6901 JSON Pointer implementation in OCaml using jsont

pp

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