RFC6901 JSON Pointer implementation in OCaml using jsont

escape

Changed files
+54 -148
doc
src
+53 -147
doc/tutorial.md
··· 11 11 ```ocaml 12 12 # open Jsont_pointer;; 13 13 # #install_printer Jsont_pointer_top.printer;; 14 + # #install_printer Jsont_pointer_top.json_printer;; 14 15 # let parse_json s = 15 16 match Jsont_bytesrw.decode_string Jsont.json s with 16 17 | Ok json -> json 17 18 | Error e -> failwith e;; 18 19 val parse_json : string -> Jsont.json = <fun> 19 - # let json_to_string json = 20 - match Jsont_bytesrw.encode_string ~format:Jsont.Minify Jsont.json json with 21 - | Ok s -> s 22 - | Error e -> failwith e;; 23 - val json_to_string : Jsont.json -> string = <fun> 24 20 ``` 25 21 26 22 ## What is JSON Pointer? ··· 44 40 ] 45 41 }|};; 46 42 val users_json : Jsont.json = 47 - Jsont.Object 48 - ([(("users", <abstr>), 49 - Jsont.Array 50 - ([Jsont.Object 51 - ([(("name", <abstr>), Jsont.String ("Alice", <abstr>)); 52 - (("age", <abstr>), Jsont.Number (30., <abstr>))], 53 - <abstr>); 54 - Jsont.Object 55 - ([(("name", <abstr>), Jsont.String ("Bob", <abstr>)); 56 - (("age", <abstr>), Jsont.Number (25., <abstr>))], 57 - <abstr>)], 58 - <abstr>))], 59 - <abstr>) 43 + {"users":[{"name":"Alice","age":30},{"name":"Bob","age":25}]} 60 44 ``` 61 45 62 46 The JSON Pointer `/users/0/name` refers to the string `"Alice"`: ··· 65 49 # let ptr = of_string "/users/0/name";; 66 50 val ptr : t = [`Mem "users"; `Nth 0; `Mem "name"] 67 51 # get ptr users_json;; 68 - - : Jsont.json = Jsont.String ("Alice", <abstr>) 52 + - : Jsont.json = "Alice" 69 53 ``` 70 54 71 55 In OCaml, this is represented by the `Jsont_pointer.t` type - a sequence ··· 186 170 "m~n": 8 187 171 }|};; 188 172 val rfc_example : Jsont.json = 189 - Jsont.Object 190 - ([(("foo", <abstr>), 191 - Jsont.Array 192 - ([Jsont.String ("bar", <abstr>); Jsont.String ("baz", <abstr>)], 193 - <abstr>)); 194 - (("", <abstr>), Jsont.Number (0., <abstr>)); 195 - (("a/b", <abstr>), Jsont.Number (1., <abstr>)); 196 - (("c%d", <abstr>), Jsont.Number (2., <abstr>)); 197 - (("e^f", <abstr>), Jsont.Number (3., <abstr>)); 198 - (("g|h", <abstr>), Jsont.Number (4., <abstr>)); 199 - (("i\\j", <abstr>), Jsont.Number (5., <abstr>)); 200 - (("k\"l", <abstr>), Jsont.Number (6., <abstr>)); 201 - ((" ", <abstr>), Jsont.Number (7., <abstr>)); 202 - (("m~n", <abstr>), Jsont.Number (8., <abstr>))], 203 - <abstr>) 173 + {"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} 204 174 ``` 205 175 206 176 This document is carefully constructed to exercise various edge cases! ··· 208 178 ### The Root Pointer 209 179 210 180 ```ocaml 211 - # get root rfc_example |> json_to_string;; 212 - - : string = 213 - "{\"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}" 181 + # get root rfc_example ;; 182 + - : Jsont.json = 183 + {"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} 214 184 ``` 215 185 216 186 The empty pointer (`root`) returns the whole document. ··· 218 188 ### Object Member Access 219 189 220 190 ```ocaml 221 - # get (of_string "/foo") rfc_example |> json_to_string;; 222 - - : string = "[\"bar\",\"baz\"]" 191 + # get (of_string "/foo") rfc_example ;; 192 + - : Jsont.json = ["bar","baz"] 223 193 ``` 224 194 225 195 `/foo` accesses the member named `foo`, which is an array. ··· 227 197 ### Array Index Access 228 198 229 199 ```ocaml 230 - # get (of_string "/foo/0") rfc_example |> json_to_string;; 231 - - : string = "\"bar\"" 232 - # get (of_string "/foo/1") rfc_example |> json_to_string;; 233 - - : string = "\"baz\"" 200 + # get (of_string "/foo/0") rfc_example ;; 201 + - : Jsont.json = "bar" 202 + # get (of_string "/foo/1") rfc_example ;; 203 + - : Jsont.json = "baz" 234 204 ``` 235 205 236 206 `/foo/0` first goes to `foo`, then accesses index 0 of the array. ··· 240 210 JSON allows empty strings as object keys: 241 211 242 212 ```ocaml 243 - # get (of_string "/") rfc_example |> json_to_string;; 244 - - : string = "0" 213 + # get (of_string "/") rfc_example ;; 214 + - : Jsont.json = 0 245 215 ``` 246 216 247 217 The pointer `/` has one token: the empty string. This accesses the member ··· 252 222 The RFC example includes keys with `/` and `~` characters: 253 223 254 224 ```ocaml 255 - # get (of_string "/a~1b") rfc_example |> json_to_string;; 256 - - : string = "1" 225 + # get (of_string "/a~1b") rfc_example ;; 226 + - : Jsont.json = 1 257 227 ``` 258 228 259 229 The token `a~1b` refers to the key `a/b`. We'll explain this escaping 260 230 [below](#escaping-special-characters). 261 231 262 232 ```ocaml 263 - # get (of_string "/m~0n") rfc_example |> json_to_string;; 264 - - : string = "8" 233 + # get (of_string "/m~0n") rfc_example ;; 234 + - : Jsont.json = 8 265 235 ``` 266 236 267 237 The token `m~0n` refers to the key `m~n`. ··· 274 244 val slash_ptr : t = [`Mem "a/b"] 275 245 # to_string slash_ptr;; 276 246 - : string = "/a~1b" 277 - # get slash_ptr rfc_example |> json_to_string;; 278 - - : string = "1" 247 + # get slash_ptr rfc_example ;; 248 + - : Jsont.json = 1 279 249 ``` 280 250 281 251 The library escapes it when converting to string. ··· 285 255 Most characters don't need escaping in JSON Pointer strings: 286 256 287 257 ```ocaml 288 - # get (of_string "/c%d") rfc_example |> json_to_string;; 289 - - : string = "2" 290 - # get (of_string "/e^f") rfc_example |> json_to_string;; 291 - - : string = "3" 292 - # get (of_string "/g|h") rfc_example |> json_to_string;; 293 - - : string = "4" 294 - # get (of_string "/ ") rfc_example |> json_to_string;; 295 - - : string = "7" 258 + # get (of_string "/c%d") rfc_example ;; 259 + - : Jsont.json = 2 260 + # get (of_string "/e^f") rfc_example ;; 261 + - : Jsont.json = 3 262 + # get (of_string "/g|h") rfc_example ;; 263 + - : Jsont.json = 4 264 + # get (of_string "/ ") rfc_example ;; 265 + - : Jsont.json = 7 296 266 ``` 297 267 298 268 Even a space is a valid key character! ··· 404 374 405 375 ```ocaml 406 376 # let obj = parse_json {|{"foo":"bar"}|};; 407 - val obj : Jsont.json = 408 - Jsont.Object ([(("foo", <abstr>), Jsont.String ("bar", <abstr>))], <abstr>) 377 + val obj : Jsont.json = {"foo":"bar"} 409 378 # add (of_string "/baz") obj ~value:(Jsont.Json.string "qux") 410 - |> json_to_string;; 411 - - : string = "{\"foo\":\"bar\",\"baz\":\"qux\"}" 379 + ;; 380 + - : Jsont.json = {"foo":"bar","baz":"qux"} 412 381 ``` 413 382 414 383 For arrays, `add` inserts BEFORE the specified index: 415 384 416 385 ```ocaml 417 386 # let arr_obj = parse_json {|{"foo":["a","b"]}|};; 418 - val arr_obj : Jsont.json = 419 - Jsont.Object 420 - ([(("foo", <abstr>), 421 - Jsont.Array 422 - ([Jsont.String ("a", <abstr>); Jsont.String ("b", <abstr>)], <abstr>))], 423 - <abstr>) 387 + val arr_obj : Jsont.json = {"foo":["a","b"]} 424 388 # add (of_string "/foo/1") arr_obj ~value:(Jsont.Json.string "X") 425 - |> json_to_string;; 426 - - : string = "{\"foo\":[\"a\",\"X\",\"b\"]}" 389 + ;; 390 + - : Jsont.json = {"foo":["a","X","b"]} 427 391 ``` 428 392 429 393 This is where the `-` marker shines - it appends to the end: 430 394 431 395 ```ocaml 432 396 # add (of_string "/foo/-") arr_obj ~value:(Jsont.Json.string "c") 433 - |> json_to_string;; 434 - - : string = "{\"foo\":[\"a\",\"b\",\"c\"]}" 397 + ;; 398 + - : Jsont.json = {"foo":["a","b","c"]} 435 399 ``` 436 400 437 401 ### Remove ··· 440 404 441 405 ```ocaml 442 406 # let two_fields = parse_json {|{"foo":"bar","baz":"qux"}|};; 443 - val two_fields : Jsont.json = 444 - Jsont.Object 445 - ([(("foo", <abstr>), Jsont.String ("bar", <abstr>)); 446 - (("baz", <abstr>), Jsont.String ("qux", <abstr>))], 447 - <abstr>) 448 - # remove (of_string "/baz") two_fields |> json_to_string;; 449 - - : string = "{\"foo\":\"bar\"}" 407 + val two_fields : Jsont.json = {"foo":"bar","baz":"qux"} 408 + # remove (of_string "/baz") two_fields ;; 409 + - : Jsont.json = {"foo":"bar"} 450 410 ``` 451 411 452 412 For arrays, it removes and shifts: 453 413 454 414 ```ocaml 455 415 # let three_elem = parse_json {|{"foo":["a","b","c"]}|};; 456 - val three_elem : Jsont.json = 457 - Jsont.Object 458 - ([(("foo", <abstr>), 459 - Jsont.Array 460 - ([Jsont.String ("a", <abstr>); Jsont.String ("b", <abstr>); 461 - Jsont.String ("c", <abstr>)], 462 - <abstr>))], 463 - <abstr>) 464 - # remove (of_string "/foo/1") three_elem |> json_to_string;; 465 - - : string = "{\"foo\":[\"a\",\"c\"]}" 416 + val three_elem : Jsont.json = {"foo":["a","b","c"]} 417 + # remove (of_string "/foo/1") three_elem ;; 418 + - : Jsont.json = {"foo":["a","c"]} 466 419 ``` 467 420 468 421 ### Replace ··· 471 424 472 425 ```ocaml 473 426 # replace (of_string "/foo") obj ~value:(Jsont.Json.string "baz") 474 - |> json_to_string;; 475 - - : string = "{\"foo\":\"baz\"}" 427 + ;; 428 + - : Jsont.json = {"foo":"baz"} 476 429 ``` 477 430 478 431 Unlike `add`, `replace` requires the target to already exist. ··· 484 437 485 438 ```ocaml 486 439 # let nested = parse_json {|{"foo":{"bar":"baz"},"qux":{}}|};; 487 - val nested : Jsont.json = 488 - Jsont.Object 489 - ([(("foo", <abstr>), 490 - Jsont.Object 491 - ([(("bar", <abstr>), Jsont.String ("baz", <abstr>))], <abstr>)); 492 - (("qux", <abstr>), Jsont.Object ([], <abstr>))], 493 - <abstr>) 440 + val nested : Jsont.json = {"foo":{"bar":"baz"},"qux":{}} 494 441 # move ~from:(of_string "/foo/bar") ~path:(of_string "/qux/thud") nested 495 - |> json_to_string;; 496 - - : string = "{\"foo\":{},\"qux\":{\"thud\":\"baz\"}}" 442 + ;; 443 + - : Jsont.json = {"foo":{},"qux":{"thud":"baz"}} 497 444 ``` 498 445 499 446 ### Copy ··· 502 449 503 450 ```ocaml 504 451 # let to_copy = parse_json {|{"foo":{"bar":"baz"}}|};; 505 - val to_copy : Jsont.json = 506 - Jsont.Object 507 - ([(("foo", <abstr>), 508 - Jsont.Object 509 - ([(("bar", <abstr>), Jsont.String ("baz", <abstr>))], <abstr>))], 510 - <abstr>) 452 + val to_copy : Jsont.json = {"foo":{"bar":"baz"}} 511 453 # copy ~from:(of_string "/foo/bar") ~path:(of_string "/foo/qux") to_copy 512 - |> json_to_string;; 513 - - : string = "{\"foo\":{\"bar\":\"baz\",\"qux\":\"baz\"}}" 454 + ;; 455 + - : Jsont.json = {"foo":{"bar":"baz","qux":"baz"}} 514 456 ``` 515 457 516 458 ### Test ··· 704 646 "features": ["auth", "logging", "metrics"] 705 647 }|};; 706 648 val config_json : Jsont.json = 707 - Jsont.Object 708 - ([(("database", <abstr>), 709 - Jsont.Object 710 - ([(("host", <abstr>), Jsont.String ("localhost", <abstr>)); 711 - (("port", <abstr>), Jsont.Number (5432., <abstr>)); 712 - (("credentials", <abstr>), 713 - Jsont.Object 714 - ([(("username", <abstr>), Jsont.String ("admin", <abstr>)); 715 - (("password", <abstr>), Jsont.String ("secret", <abstr>))], 716 - <abstr>))], 717 - <abstr>)); 718 - (("features", <abstr>), 719 - Jsont.Array 720 - ([Jsont.String ("auth", <abstr>); Jsont.String ("logging", <abstr>); 721 - Jsont.String ("metrics", <abstr>)], 722 - <abstr>))], 723 - <abstr>) 649 + {"database":{"host":"localhost","port":5432,"credentials":{"username":"admin","password":"secret"}},"features":["auth","logging","metrics"]} 724 650 ``` 725 651 726 652 ### Typed Access with `path` ··· 778 704 } 779 705 }|};; 780 706 val org_json : Jsont.json = 781 - Jsont.Object 782 - ([(("organization", <abstr>), 783 - Jsont.Object 784 - ([(("owner", <abstr>), 785 - Jsont.Object 786 - ([(("name", <abstr>), Jsont.String ("Alice", <abstr>)); 787 - (("email", <abstr>), 788 - Jsont.String ("alice@example.com", <abstr>)); 789 - (("age", <abstr>), Jsont.Number (35., <abstr>))], 790 - <abstr>)); 791 - (("members", <abstr>), 792 - Jsont.Array 793 - ([Jsont.Object 794 - ([(("name", <abstr>), Jsont.String ("Bob", <abstr>)); 795 - (("email", <abstr>), 796 - Jsont.String ("bob@example.com", <abstr>)); 797 - (("age", <abstr>), Jsont.Number (28., <abstr>))], 798 - <abstr>)], 799 - <abstr>))], 800 - <abstr>))], 801 - <abstr>) 707 + {"organization":{"owner":{"name":"Alice","email":"alice@example.com","age":35},"members":[{"name":"Bob","email":"bob@example.com","age":28}]}} 802 708 # Jsont.Json.decode 803 709 (path (of_string "/organization/owner/name") Jsont.string) 804 710 org_json
+1 -1
src/jsont_pointer.ml
··· 255 255 256 256 let pp_verbose ppf p = 257 257 let pp_index ppf = function 258 - | `Mem s -> Format.fprintf ppf "`Mem %S" s 258 + | `Mem s -> Format.fprintf ppf {|`Mem "%s"|} s 259 259 | `Nth n -> Format.fprintf ppf "`Nth %d" n 260 260 | `End -> Format.fprintf ppf "`End" 261 261 in