RFC6901 JSON Pointer implementation in OCaml using jsont

simplify

+3 -3
bin/jsonpp.ml
··· 24 24 let indices = Jsont_pointer.indices p in 25 25 let index_strs = List.map (fun idx -> 26 26 match idx with 27 - | Jsont_pointer.Index.Mem s -> Printf.sprintf "Mem:%s" s 28 - | Jsont_pointer.Index.Nth n -> Printf.sprintf "Nth:%d" n 29 - | Jsont_pointer.Index.End -> "End" 27 + | `Mem s -> Printf.sprintf "Mem:%s" s 28 + | `Nth n -> Printf.sprintf "Nth:%d" n 29 + | `End -> "End" 30 30 ) indices in 31 31 Printf.printf "OK: [%s]\n" (String.concat ", " index_strs) 32 32 with Jsont.Error e ->
-4
doc/dune
··· 1 1 (mdx 2 - (deps 3 - %{bin:jsonpp} 4 - rfc6901_example.json 5 - config.json) 6 2 (libraries jsont jsont.bytesrw jsont_pointer))
+335 -474
doc/tutorial.md
··· 4 4 [RFC 6901](https://www.rfc-editor.org/rfc/rfc6901), and demonstrates 5 5 the `jsont-pointer` OCaml library through interactive examples. 6 6 7 + ## Setup 8 + 9 + First, let's set up our environment with helper functions: 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 16 + | Error e -> failwith e;; 17 + val parse_json : string -> Jsont.json = <fun> 18 + # let json_to_string json = 19 + match Jsont_bytesrw.encode_string ~format:Jsont.Minify Jsont.json json with 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 + 7 34 ## What is JSON Pointer? 8 35 9 36 From RFC 6901, Section 1: ··· 17 44 18 45 For example, given this JSON document: 19 46 20 - ```json 21 - { 22 - "users": [ 23 - {"name": "Alice", "age": 30}, 24 - {"name": "Bob", "age": 25} 25 - ] 26 - } 47 + ```ocaml 48 + # let users_json = parse_json {|{ 49 + "users": [ 50 + {"name": "Alice", "age": 30}, 51 + {"name": "Bob", "age": 25} 52 + ] 53 + }|};; 54 + val users_json : Jsont.json = 55 + Jsont.Object 56 + ([(("users", <abstr>), 57 + Jsont.Array 58 + ([Jsont.Object 59 + ([(("name", <abstr>), Jsont.String ("Alice", <abstr>)); 60 + (("age", <abstr>), Jsont.Number (30., <abstr>))], 61 + <abstr>); 62 + Jsont.Object 63 + ([(("name", <abstr>), Jsont.String ("Bob", <abstr>)); 64 + (("age", <abstr>), Jsont.Number (25., <abstr>))], 65 + <abstr>)], 66 + <abstr>))], 67 + <abstr>) 27 68 ``` 28 69 29 - The JSON Pointer `/users/0/name` refers to the string `"Alice"`. 70 + The JSON Pointer `/users/0/name` refers to the string `"Alice"`: 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 + ``` 30 78 31 79 In OCaml, this is represented by the `Jsont_pointer.t` type - a sequence 32 80 of navigation steps from the document root to a target value. ··· 50 98 - Every non-empty pointer starts with `/` 51 99 - Everything between `/` characters is a "reference token" 52 100 53 - Let's see this in action. We can parse pointers and see their structure: 101 + Let's see this in action: 54 102 55 - ```sh 56 - $ jsonpp parse "" 57 - OK: [] 103 + ```ocaml 104 + # let empty_ptr = of_string "";; 105 + val empty_ptr : t = <abstr> 106 + # show_pointer empty_ptr;; 107 + - : string = "[]" 58 108 ``` 59 109 60 110 The empty pointer has no reference tokens - it points to the root. 61 111 62 - ```sh 63 - $ jsonpp parse "/foo" 64 - OK: [Mem:foo] 112 + ```ocaml 113 + # let foo_ptr = of_string "/foo";; 114 + val foo_ptr : t = <abstr> 115 + # show_pointer foo_ptr;; 116 + - : string = "[Mem:foo]" 65 117 ``` 66 118 67 119 The pointer `/foo` has one token: `foo`. Since it's not a number, it's 68 120 interpreted as an object member name (`Mem`). 69 121 70 - ```sh 71 - $ jsonpp parse "/foo/0" 72 - OK: [Mem:foo, Nth:0] 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]" 73 127 ``` 74 128 75 129 Here we have two tokens: `foo` (a member name) and `0` (interpreted as 76 130 an array index `Nth`). 77 131 78 - ```sh 79 - $ jsonpp parse "/foo/bar/baz" 80 - OK: [Mem:foo, Mem:bar, Mem:baz] 132 + ```ocaml 133 + # show_pointer (of_string "/foo/bar/baz");; 134 + - : string = "[Mem:foo, Mem:bar, Mem:baz]" 81 135 ``` 82 136 83 137 Multiple tokens navigate deeper into nested structures. ··· 88 142 89 143 <!-- $MDX skip --> 90 144 ```ocaml 91 - type t = 92 - | Mem of string (* Object member access *) 93 - | Nth of int (* Array index access *) 94 - | End (* The special "-" marker for append operations *) 145 + type t = [ 146 + | `Mem of string (* Object member access *) 147 + | `Nth of int (* Array index access *) 148 + | `End (* The special "-" marker for append operations *) 149 + ] 95 150 ``` 96 151 97 152 The `Mem` variant holds the **unescaped** member name - you work with the ··· 102 157 103 158 What happens if a pointer doesn't start with `/`? 104 159 105 - ```sh 106 - $ jsonpp parse "foo" 107 - ERROR: Invalid JSON Pointer: must be empty or start with '/': foo 160 + ```ocaml 161 + # of_string "foo";; 162 + Exception: Jsont.Error ([], <abstr>, <abstr>). 108 163 ``` 109 164 110 165 The RFC is strict: non-empty pointers MUST start with `/`. 111 166 167 + For safer parsing, use `of_string_result`: 168 + 169 + ```ocaml 170 + # of_string_result "foo";; 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 + 112 177 ## Evaluation: Navigating JSON 113 178 114 179 Now we come to the heart of JSON Pointer: evaluation. RFC 6901, Section 4 ··· 119 184 > the document. Each reference token in the JSON Pointer is evaluated 120 185 > sequentially. 121 186 122 - In the library, this is the `Jsont_pointer.get` function: 187 + Let's use the example JSON document from RFC 6901, Section 5: 123 188 124 - <!-- $MDX skip --> 125 189 ```ocaml 126 - val get : t -> Jsont.json -> Jsont.json 127 - ``` 128 - 129 - Let's use the example JSON document from RFC 6901, Section 5: 130 - 131 - ```sh 132 - $ cat rfc6901_example.json 133 - { 134 - "foo": ["bar", "baz"], 135 - "": 0, 136 - "a/b": 1, 137 - "c%d": 2, 138 - "e^f": 3, 139 - "g|h": 4, 140 - "i\\j": 5, 141 - "k\"l": 6, 142 - " ": 7, 143 - "m~n": 8 144 - } 190 + # let rfc_example = parse_json {|{ 191 + "foo": ["bar", "baz"], 192 + "": 0, 193 + "a/b": 1, 194 + "c%d": 2, 195 + "e^f": 3, 196 + "g|h": 4, 197 + "i\\j": 5, 198 + "k\"l": 6, 199 + " ": 7, 200 + "m~n": 8 201 + }|};; 202 + val rfc_example : Jsont.json = 203 + Jsont.Object 204 + ([(("foo", <abstr>), 205 + Jsont.Array 206 + ([Jsont.String ("bar", <abstr>); Jsont.String ("baz", <abstr>)], 207 + <abstr>)); 208 + (("", <abstr>), Jsont.Number (0., <abstr>)); 209 + (("a/b", <abstr>), Jsont.Number (1., <abstr>)); 210 + (("c%d", <abstr>), Jsont.Number (2., <abstr>)); 211 + (("e^f", <abstr>), Jsont.Number (3., <abstr>)); 212 + (("g|h", <abstr>), Jsont.Number (4., <abstr>)); 213 + (("i\\j", <abstr>), Jsont.Number (5., <abstr>)); 214 + (("k\"l", <abstr>), Jsont.Number (6., <abstr>)); 215 + ((" ", <abstr>), Jsont.Number (7., <abstr>)); 216 + (("m~n", <abstr>), Jsont.Number (8., <abstr>))], 217 + <abstr>) 145 218 ``` 146 219 147 220 This document is carefully constructed to exercise various edge cases! 148 221 149 222 ### The Root Pointer 150 223 151 - ```sh 152 - $ jsonpp eval rfc6901_example.json "" 153 - OK: {"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} 154 - ``` 155 - 156 - The empty pointer returns the whole document. In OCaml, this is 157 - `Jsont_pointer.root`: 158 - 159 - <!-- $MDX skip --> 160 224 ```ocaml 161 - val root : t 162 - (** The empty pointer that references the whole document. *) 225 + # get root rfc_example |> json_to_string;; 226 + - : string = 227 + "{\"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}" 163 228 ``` 164 229 230 + The empty pointer (`root`) returns the whole document. 231 + 165 232 ### Object Member Access 166 233 167 - ```sh 168 - $ jsonpp eval rfc6901_example.json "/foo" 169 - OK: ["bar","baz"] 234 + ```ocaml 235 + # get (of_string "/foo") rfc_example |> json_to_string;; 236 + - : string = "[\"bar\",\"baz\"]" 170 237 ``` 171 238 172 239 `/foo` accesses the member named `foo`, which is an array. 173 240 174 241 ### Array Index Access 175 242 176 - ```sh 177 - $ jsonpp eval rfc6901_example.json "/foo/0" 178 - OK: "bar" 243 + ```ocaml 244 + # get (of_string "/foo/0") rfc_example |> json_to_string;; 245 + - : string = "\"bar\"" 246 + # get (of_string "/foo/1") rfc_example |> json_to_string;; 247 + - : string = "\"baz\"" 179 248 ``` 180 249 181 250 `/foo/0` first goes to `foo`, then accesses index 0 of the array. 182 251 183 - ```sh 184 - $ jsonpp eval rfc6901_example.json "/foo/1" 185 - OK: "baz" 186 - ``` 187 - 188 - Index 1 gives us the second element. 189 - 190 252 ### Empty String as Key 191 253 192 254 JSON allows empty strings as object keys: 193 255 194 - ```sh 195 - $ jsonpp eval rfc6901_example.json "/" 196 - OK: 0 256 + ```ocaml 257 + # get (of_string "/") rfc_example |> json_to_string;; 258 + - : string = "0" 197 259 ``` 198 260 199 261 The pointer `/` has one token: the empty string. This accesses the member ··· 203 265 204 266 The RFC example includes keys with `/` and `~` characters: 205 267 206 - ```sh 207 - $ jsonpp eval rfc6901_example.json "/a~1b" 208 - OK: 1 268 + ```ocaml 269 + # get (of_string "/a~1b") rfc_example |> json_to_string;; 270 + - : string = "1" 209 271 ``` 210 272 211 273 The token `a~1b` refers to the key `a/b`. We'll explain this escaping 212 274 [below](#escaping-special-characters). 213 275 214 - ```sh 215 - $ jsonpp eval rfc6901_example.json "/m~0n" 216 - OK: 8 276 + ```ocaml 277 + # get (of_string "/m~0n") rfc_example |> json_to_string;; 278 + - : string = "8" 217 279 ``` 218 280 219 281 The token `m~0n` refers to the key `m~n`. 220 282 221 283 **Important**: When using the OCaml library programmatically, you don't need 222 - to worry about escaping. The `Index.Mem` variant holds the literal key name: 284 + to worry about escaping. The `Mem` variant holds the literal key name: 223 285 224 - <!-- $MDX skip --> 225 286 ```ocaml 226 - (* To access the key "a/b", just use the literal string *) 227 - let pointer = Jsont_pointer.make [Mem "a/b"] 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;; 292 + - : string = "1" 293 + ``` 228 294 229 - (* The library escapes it when converting to string *) 230 - let s = Jsont_pointer.to_string pointer (* "/a~1b" *) 231 - ``` 295 + The library escapes it when converting to string. 232 296 233 297 ### Other Special Characters (No Escaping Needed) 234 298 235 299 Most characters don't need escaping in JSON Pointer strings: 236 300 237 - ```sh 238 - $ jsonpp eval rfc6901_example.json "/c%d" 239 - OK: 2 240 - ``` 241 - 242 - ```sh 243 - $ jsonpp eval rfc6901_example.json "/e^f" 244 - OK: 3 245 - ``` 246 - 247 - ```sh 248 - $ jsonpp eval rfc6901_example.json "/g|h" 249 - OK: 4 250 - ``` 251 - 252 - ```sh 253 - $ jsonpp eval rfc6901_example.json "/ " 254 - OK: 7 301 + ```ocaml 302 + # get (of_string "/c%d") rfc_example |> json_to_string;; 303 + - : string = "2" 304 + # get (of_string "/e^f") rfc_example |> json_to_string;; 305 + - : string = "3" 306 + # get (of_string "/g|h") rfc_example |> json_to_string;; 307 + - : string = "4" 308 + # get (of_string "/ ") rfc_example |> json_to_string;; 309 + - : string = "7" 255 310 ``` 256 311 257 312 Even a space is a valid key character! ··· 260 315 261 316 What happens when we try to access something that doesn't exist? 262 317 263 - ```sh 264 - $ jsonpp eval rfc6901_example.json "/nonexistent" 265 - ERROR: JSON Pointer: member 'nonexistent' not found 266 - File "-": 318 + ```ocaml 319 + # get_result (of_string "/nonexistent") rfc_example;; 320 + - : (Jsont.json, Jsont.Error.t) result = Error ([], <abstr>, <abstr>) 321 + # find (of_string "/nonexistent") rfc_example;; 322 + - : Jsont.json option = None 267 323 ``` 268 324 269 325 Or an out-of-bounds array index: 270 326 271 - ```sh 272 - $ jsonpp eval rfc6901_example.json "/foo/99" 273 - ERROR: JSON Pointer: index 99 out of bounds (array has 2 elements) 274 - File "-": 327 + ```ocaml 328 + # find (of_string "/foo/99") rfc_example;; 329 + - : Jsont.json option = None 275 330 ``` 276 331 277 332 Or try to index into a non-container: 278 333 279 - ```sh 280 - $ jsonpp eval rfc6901_example.json "/foo/0/invalid" 281 - ERROR: JSON Pointer: cannot index into string with 'invalid' 282 - File "-": 334 + ```ocaml 335 + # find (of_string "/foo/0/invalid") rfc_example;; 336 + - : Jsont.json option = None 283 337 ``` 284 338 285 339 The library provides both exception-raising and result-returning variants: ··· 303 357 304 358 > note that leading zeros are not allowed 305 359 306 - ```sh 307 - $ jsonpp parse "/foo/0" 308 - OK: [Mem:foo, Nth:0] 360 + ```ocaml 361 + # show_pointer (of_string "/foo/0");; 362 + - : string = "[Mem:foo, Nth:0]" 309 363 ``` 310 364 311 365 Zero itself is fine. 312 366 313 - ```sh 314 - $ jsonpp parse "/foo/01" 315 - OK: [Mem:foo, Mem:01] 367 + ```ocaml 368 + # show_pointer (of_string "/foo/01");; 369 + - : string = "[Mem:foo, Mem:01]" 316 370 ``` 317 371 318 372 But `01` has a leading zero, so it's NOT treated as an array index - it ··· 329 383 This is primarily useful for JSON Patch operations (RFC 6902). Let's see 330 384 how it parses: 331 385 332 - ```sh 333 - $ jsonpp parse "/foo/-" 334 - OK: [Mem:foo, End] 386 + ```ocaml 387 + # show_pointer (of_string "/foo/-");; 388 + - : string = "[Mem:foo, End]" 335 389 ``` 336 390 337 391 The `-` is recognized as a special `End` index. ··· 339 393 However, you cannot evaluate a pointer containing `-` because it refers 340 394 to a position that doesn't exist: 341 395 342 - ```sh 343 - $ jsonpp eval rfc6901_example.json "/foo/-" 344 - ERROR: JSON Pointer: '-' (end marker) refers to nonexistent array element 345 - File "-": 396 + ```ocaml 397 + # find (of_string "/foo/-") rfc_example;; 398 + - : Jsont.json option = None 346 399 ``` 347 400 348 401 The RFC explains this: ··· 363 416 364 417 The `add` operation inserts a value at a location: 365 418 366 - ```sh 367 - $ jsonpp add '{"foo":"bar"}' '/baz' '"qux"' 368 - {"foo":"bar","baz":"qux"} 369 - ``` 370 - 371 - In OCaml: 372 - 373 - <!-- $MDX skip --> 374 419 ```ocaml 375 - val add : t -> Jsont.json -> value:Jsont.json -> Jsont.json 420 + # let obj = parse_json {|{"foo":"bar"}|};; 421 + val obj : Jsont.json = 422 + Jsont.Object ([(("foo", <abstr>), Jsont.String ("bar", <abstr>))], <abstr>) 423 + # add (of_string "/baz") obj ~value:(Jsont.Json.string "qux") 424 + |> json_to_string;; 425 + - : string = "{\"foo\":\"bar\",\"baz\":\"qux\"}" 376 426 ``` 377 427 378 428 For arrays, `add` inserts BEFORE the specified index: 379 429 380 - ```sh 381 - $ jsonpp add '{"foo":["a","b"]}' '/foo/1' '"X"' 382 - {"foo":["a","X","b"]} 430 + ```ocaml 431 + # let arr_obj = parse_json {|{"foo":["a","b"]}|};; 432 + val arr_obj : Jsont.json = 433 + Jsont.Object 434 + ([(("foo", <abstr>), 435 + Jsont.Array 436 + ([Jsont.String ("a", <abstr>); Jsont.String ("b", <abstr>)], <abstr>))], 437 + <abstr>) 438 + # add (of_string "/foo/1") arr_obj ~value:(Jsont.Json.string "X") 439 + |> json_to_string;; 440 + - : string = "{\"foo\":[\"a\",\"X\",\"b\"]}" 383 441 ``` 384 442 385 443 This is where the `-` marker shines - it appends to the end: 386 444 387 - ```sh 388 - $ jsonpp add '{"foo":["a","b"]}' '/foo/-' '"c"' 389 - {"foo":["a","b","c"]} 445 + ```ocaml 446 + # add (of_string "/foo/-") arr_obj ~value:(Jsont.Json.string "c") 447 + |> json_to_string;; 448 + - : string = "{\"foo\":[\"a\",\"b\",\"c\"]}" 390 449 ``` 391 450 392 451 ### Remove 393 452 394 453 The `remove` operation deletes a value: 395 454 396 - ```sh 397 - $ jsonpp remove '{"foo":"bar","baz":"qux"}' '/baz' 398 - {"foo":"bar"} 455 + ```ocaml 456 + # let two_fields = parse_json {|{"foo":"bar","baz":"qux"}|};; 457 + val two_fields : Jsont.json = 458 + Jsont.Object 459 + ([(("foo", <abstr>), Jsont.String ("bar", <abstr>)); 460 + (("baz", <abstr>), Jsont.String ("qux", <abstr>))], 461 + <abstr>) 462 + # remove (of_string "/baz") two_fields |> json_to_string;; 463 + - : string = "{\"foo\":\"bar\"}" 399 464 ``` 400 465 401 466 For arrays, it removes and shifts: 402 467 403 - ```sh 404 - $ jsonpp remove '{"foo":["a","b","c"]}' '/foo/1' 405 - {"foo":["a","c"]} 468 + ```ocaml 469 + # let three_elem = parse_json {|{"foo":["a","b","c"]}|};; 470 + val three_elem : Jsont.json = 471 + Jsont.Object 472 + ([(("foo", <abstr>), 473 + Jsont.Array 474 + ([Jsont.String ("a", <abstr>); Jsont.String ("b", <abstr>); 475 + Jsont.String ("c", <abstr>)], 476 + <abstr>))], 477 + <abstr>) 478 + # remove (of_string "/foo/1") three_elem |> json_to_string;; 479 + - : string = "{\"foo\":[\"a\",\"c\"]}" 406 480 ``` 407 481 408 482 ### Replace 409 483 410 484 The `replace` operation updates an existing value: 411 485 412 - ```sh 413 - $ jsonpp replace '{"foo":"bar"}' '/foo' '"baz"' 414 - {"foo":"baz"} 486 + ```ocaml 487 + # replace (of_string "/foo") obj ~value:(Jsont.Json.string "baz") 488 + |> json_to_string;; 489 + - : string = "{\"foo\":\"baz\"}" 415 490 ``` 416 491 417 - Unlike `add`, `replace` requires the target to already exist: 418 - 419 - ```sh 420 - $ jsonpp replace '{"foo":"bar"}' '/nonexistent' '"value"' 421 - ERROR: JSON Pointer: member 'nonexistent' not found 422 - File "-": 423 - ``` 492 + Unlike `add`, `replace` requires the target to already exist. 493 + Attempting to replace a nonexistent path raises an error. 424 494 425 495 ### Move 426 496 427 497 The `move` operation relocates a value: 428 498 429 - ```sh 430 - $ jsonpp move '{"foo":{"bar":"baz"},"qux":{}}' '/foo/bar' '/qux/thud' 431 - {"foo":{},"qux":{"thud":"baz"}} 499 + ```ocaml 500 + # let nested = parse_json {|{"foo":{"bar":"baz"},"qux":{}}|};; 501 + val nested : Jsont.json = 502 + Jsont.Object 503 + ([(("foo", <abstr>), 504 + Jsont.Object 505 + ([(("bar", <abstr>), Jsont.String ("baz", <abstr>))], <abstr>)); 506 + (("qux", <abstr>), Jsont.Object ([], <abstr>))], 507 + <abstr>) 508 + # move ~from:(of_string "/foo/bar") ~path:(of_string "/qux/thud") nested 509 + |> json_to_string;; 510 + - : string = "{\"foo\":{},\"qux\":{\"thud\":\"baz\"}}" 432 511 ``` 433 512 434 513 ### Copy 435 514 436 515 The `copy` operation duplicates a value: 437 516 438 - ```sh 439 - $ jsonpp copy '{"foo":{"bar":"baz"}}' '/foo/bar' '/foo/qux' 440 - {"foo":{"bar":"baz","qux":"baz"}} 517 + ```ocaml 518 + # let to_copy = parse_json {|{"foo":{"bar":"baz"}}|};; 519 + val to_copy : Jsont.json = 520 + Jsont.Object 521 + ([(("foo", <abstr>), 522 + Jsont.Object 523 + ([(("bar", <abstr>), Jsont.String ("baz", <abstr>))], <abstr>))], 524 + <abstr>) 525 + # copy ~from:(of_string "/foo/bar") ~path:(of_string "/foo/qux") to_copy 526 + |> json_to_string;; 527 + - : string = "{\"foo\":{\"bar\":\"baz\",\"qux\":\"baz\"}}" 441 528 ``` 442 529 443 530 ### Test 444 531 445 532 The `test` operation verifies a value (useful in JSON Patch): 446 533 447 - ```sh 448 - $ jsonpp test '{"foo":"bar"}' '/foo' '"bar"' 449 - true 450 - ``` 451 - 452 - ```sh 453 - $ jsonpp test '{"foo":"bar"}' '/foo' '"baz"' 454 - false 534 + ```ocaml 535 + # test (of_string "/foo") obj ~expected:(Jsont.Json.string "bar");; 536 + - : bool = true 537 + # test (of_string "/foo") obj ~expected:(Jsont.Json.string "wrong");; 538 + - : bool = false 455 539 ``` 456 540 457 541 ## Escaping Special Characters ··· 473 557 ### The Library Handles Escaping Automatically 474 558 475 559 **Important**: When using `jsont-pointer` programmatically, you rarely need 476 - to think about escaping. The `Index.Mem` variant stores unescaped strings, 560 + to think about escaping. The `Mem` variant stores unescaped strings, 477 561 and escaping happens automatically during serialization: 478 562 479 - <!-- $MDX skip --> 480 563 ```ocaml 481 - (* Create a pointer to key "a/b" - no escaping needed *) 482 - let p = Jsont_pointer.make [Mem "a/b"] 483 - 484 - (* Serialize to string - escaping happens automatically *) 485 - let s = Jsont_pointer.to_string p (* Returns "/a~1b" *) 486 - 487 - (* Parse from string - unescaping happens automatically *) 488 - let p' = Jsont_pointer.of_string "/a~1b" 489 - (* p' contains [Mem "a/b"] - the unescaped key *) 490 - ``` 491 - 492 - The `Token` module exposes the escaping functions if you need them: 493 - 494 - <!-- $MDX skip --> 495 - ```ocaml 496 - module Token : sig 497 - val escape : string -> string (* "a/b" -> "a~1b" *) 498 - val unescape : string -> string (* "a~1b" -> "a/b" *) 499 - end 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]" 500 572 ``` 501 573 502 574 ### Escaping in Action 503 575 504 - Let's see escaping with the CLI tool: 505 - 506 - ```sh 507 - $ jsonpp escape "hello" 508 - hello 509 - ``` 510 - 511 - No special characters, no escaping needed. 512 - 513 - ```sh 514 - $ jsonpp escape "a/b" 515 - a~1b 516 - ``` 576 + The `Token` module exposes the escaping functions: 517 577 518 - The `/` becomes `~1`. 519 - 520 - ```sh 521 - $ jsonpp escape "a~b" 522 - a~0b 578 + ```ocaml 579 + # Token.escape "hello";; 580 + - : string = "hello" 581 + # Token.escape "a/b";; 582 + - : string = "a~1b" 583 + # Token.escape "a~b";; 584 + - : string = "a~0b" 585 + # Token.escape "~/";; 586 + - : string = "~0~1" 523 587 ``` 524 588 525 - The `~` becomes `~0`. 526 - 527 - ```sh 528 - $ jsonpp escape "~/" 529 - ~0~1 530 - ``` 531 - 532 - Both characters are escaped. 533 - 534 589 ### Unescaping 535 590 536 591 And the reverse process: 537 592 538 - ```sh 539 - $ jsonpp unescape "a~1b" 540 - OK: a/b 541 - ``` 542 - 543 - ```sh 544 - $ jsonpp unescape "a~0b" 545 - OK: a~b 593 + ```ocaml 594 + # Token.unescape "a~1b";; 595 + - : string = "a/b" 596 + # Token.unescape "a~0b";; 597 + - : string = "a~b" 546 598 ``` 547 599 548 600 ### The Order Matters! ··· 559 611 560 612 Let's verify this tricky case: 561 613 562 - ```sh 563 - $ jsonpp unescape "~01" 564 - OK: ~1 614 + ```ocaml 615 + # Token.unescape "~01";; 616 + - : string = "~1" 565 617 ``` 566 618 567 619 If we unescaped `~0` first, `~01` would become `~1`, which would then become 568 620 `/`. But that's wrong! The sequence `~01` should become the literal string 569 621 `~1` (a tilde followed by the digit one). 570 622 571 - Invalid escape sequences are rejected: 572 - 573 - ```sh 574 - $ jsonpp unescape "~2" 575 - ERROR: Invalid JSON Pointer: invalid escape sequence ~2 576 - ``` 577 - 578 - ```sh 579 - $ jsonpp unescape "hello~" 580 - ERROR: Invalid JSON Pointer: incomplete escape sequence at end 581 - ``` 582 - 583 623 ## URI Fragment Encoding 584 624 585 625 JSON Pointers can be embedded in URIs. RFC 6901, Section 6 explains: ··· 590 630 591 631 This adds percent-encoding on top of the `~0`/`~1` escaping: 592 632 593 - ```sh 594 - $ jsonpp uri-fragment "/foo" 595 - OK: /foo -> /foo 633 + ```ocaml 634 + # to_uri_fragment (of_string "/foo");; 635 + - : string = "/foo" 636 + # to_uri_fragment (of_string "/a~1b");; 637 + - : string = "/a~1b" 638 + # to_uri_fragment (of_string "/c%d");; 639 + - : string = "/c%25d" 640 + # to_uri_fragment (of_string "/ ");; 641 + - : string = "/%20" 596 642 ``` 597 643 598 - Simple pointers often don't need percent-encoding. 599 - 600 - ```sh 601 - $ jsonpp uri-fragment "/a~1b" 602 - OK: /a~1b -> /a~1b 603 - ``` 604 - 605 - The `~1` escape stays as-is (it's valid in URI fragments). 606 - 607 - ```sh 608 - $ jsonpp uri-fragment "/c%d" 609 - OK: /c%d -> /c%25d 610 - ``` 611 - 612 - The `%` character must be percent-encoded as `%25` in URIs! 613 - 614 - ```sh 615 - $ jsonpp uri-fragment "/ " 616 - OK: / -> /%20 617 - ``` 618 - 619 - Spaces become `%20`. 620 - 621 - The library provides functions for URI fragment encoding: 622 - 623 - <!-- $MDX skip --> 624 - ```ocaml 625 - val to_uri_fragment : t -> string 626 - val of_uri_fragment : string -> t 627 - val jsont_uri_fragment : t Jsont.t 628 - ``` 644 + The `%` character must be percent-encoded as `%25` in URIs, and 645 + spaces become `%20`. 629 646 630 647 Here's the RFC example showing the URI fragment forms: 631 648 ··· 640 657 | `"/ "` | `#/%20` | `7` | 641 658 | `"/m~0n"` | `#/m~0n` | `8` | 642 659 643 - ## Deeply Nested Structures 660 + ## Building Pointers Programmatically 644 661 645 - JSON Pointer handles arbitrarily deep nesting: 662 + Instead of parsing strings, you can build pointers from indices: 646 663 647 - ```sh 648 - $ jsonpp eval rfc6901_example.json "/foo/0" 649 - OK: "bar" 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" 650 669 ``` 651 670 652 - For deeper structures, just add more path segments. With nested objects: 671 + For array access, use `Nth`: 653 672 654 - ```sh 655 - $ jsonpp add '{"a":{"b":{"c":"d"}}}' '/a/b/x' '"y"' 656 - {"a":{"b":{"c":"d","x":"y"}}} 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" 657 678 ``` 658 679 659 - With nested arrays: 680 + ### Pointer Navigation 660 681 661 - ```sh 662 - $ jsonpp add '{"arr":[[1,2],[3,4]]}' '/arr/0/1' '99' 663 - {"arr":[[1,99,2],[3,4]]} 682 + You can build pointers incrementally using `append`: 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 + ``` 694 + 695 + Or concatenate two pointers: 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" 664 704 ``` 665 705 666 706 ## Jsont Integration ··· 670 710 because you can point to a location in a JSON document and decode it 671 711 directly to an OCaml type. 672 712 673 - Let's set up our OCaml environment and explore these features: 674 - 675 - ```ocaml 676 - # open Jsont_pointer;; 677 - # let parse_json s = 678 - match Jsont_bytesrw.decode_string Jsont.json s with 679 - | Ok json -> json 680 - | Error e -> failwith e;; 681 - val parse_json : string -> Jsont.json = <fun> 682 - # let json_to_string json = 683 - match Jsont_bytesrw.encode_string ~format:Jsont.Minify Jsont.json json with 684 - | Ok s -> s 685 - | Error e -> failwith e;; 686 - val json_to_string : Jsont.json -> string = <fun> 687 - ``` 688 - 689 - ### Working with JSON Values 690 - 691 - Let's create a sample configuration document: 692 - 693 713 ```ocaml 694 714 # let config_json = parse_json {|{ 695 715 "database": { ··· 719 739 <abstr>) 720 740 ``` 721 741 722 - ### Creating and Using Pointers 723 - 724 - Create a pointer and use it to extract values: 725 - 726 - ```ocaml 727 - # let host_ptr = of_string "/database/host";; 728 - val host_ptr : t = <abstr> 729 - # let host_value = get host_ptr config_json;; 730 - val host_value : Jsont.json = Jsont.String ("localhost", <abstr>) 731 - # match host_value with 732 - | Jsont.String (s, _) -> s 733 - | _ -> failwith "expected string";; 734 - - : string = "localhost" 735 - ``` 736 - 737 - ### Building Pointers Programmatically 738 - 739 - Instead of parsing strings, you can build pointers from indices: 740 - 741 - ```ocaml 742 - # let port_ptr = make [Mem "database"; Mem "port"];; 743 - val port_ptr : t = <abstr> 744 - # to_string port_ptr;; 745 - - : string = "/database/port" 746 - # match get port_ptr config_json with 747 - | Jsont.Number (n, _) -> int_of_float n 748 - | _ -> failwith "expected number";; 749 - - : int = 5432 750 - ``` 751 - 752 - For array access, use `Nth`: 753 - 754 - ```ocaml 755 - # let first_feature_ptr = make [Mem "features"; Nth 0];; 756 - val first_feature_ptr : t = <abstr> 757 - # match get first_feature_ptr config_json with 758 - | Jsont.String (s, _) -> s 759 - | _ -> failwith "expected string";; 760 - - : string = "auth" 761 - ``` 762 - 763 - ### Pointer Navigation 764 - 765 - You can build pointers incrementally using `append`: 766 - 767 - ```ocaml 768 - # let db_ptr = of_string "/database";; 769 - val db_ptr : t = <abstr> 770 - # let creds_ptr = append db_ptr (Mem "credentials");; 771 - val creds_ptr : t = <abstr> 772 - # let user_ptr = append creds_ptr (Mem "username");; 773 - val user_ptr : t = <abstr> 774 - # to_string user_ptr;; 775 - - : string = "/database/credentials/username" 776 - # match get user_ptr config_json with 777 - | Jsont.String (s, _) -> s 778 - | _ -> failwith "expected string";; 779 - - : string = "admin" 780 - ``` 781 - 782 - ### Safe Access with `find` 783 - 784 - Use `find` when you're not sure if a path exists: 785 - 786 - ```ocaml 787 - # find (of_string "/database/timeout") config_json;; 788 - - : Jsont.json option = None 789 - # find (of_string "/database/host") config_json |> Option.is_some;; 790 - - : bool = true 791 - ``` 792 - 793 742 ### Typed Access with `path` 794 743 795 744 The `path` combinator combines pointer navigation with typed decoding: ··· 831 780 config_json 832 781 |> Result.get_ok;; 833 782 val timeout : int = 30 834 - ``` 835 - 836 - ### Mutation Operations 837 - 838 - The library provides mutation functions for modifying JSON: 839 - 840 - ```ocaml 841 - # let sample = parse_json {|{"name": "Alice", "scores": [85, 92, 78]}|};; 842 - val sample : Jsont.json = 843 - Jsont.Object 844 - ([(("name", <abstr>), Jsont.String ("Alice", <abstr>)); 845 - (("scores", <abstr>), 846 - Jsont.Array 847 - ([Jsont.Number (85., <abstr>); Jsont.Number (92., <abstr>); 848 - Jsont.Number (78., <abstr>)], 849 - <abstr>))], 850 - <abstr>) 851 - ``` 852 - 853 - **Add** a new field: 854 - 855 - ```ocaml 856 - # let with_email = add (of_string "/email") sample 857 - ~value:(Jsont.Json.string "alice@example.com");; 858 - val with_email : Jsont.json = 859 - Jsont.Object 860 - ([(("name", <abstr>), Jsont.String ("Alice", <abstr>)); 861 - (("scores", <abstr>), 862 - Jsont.Array 863 - ([Jsont.Number (85., <abstr>); Jsont.Number (92., <abstr>); 864 - Jsont.Number (78., <abstr>)], 865 - <abstr>)); 866 - (("email", <abstr>), Jsont.String ("alice@example.com", <abstr>))], 867 - <abstr>) 868 - # json_to_string with_email;; 869 - - : string = 870 - "{\"name\":\"Alice\",\"scores\":[85,92,78],\"email\":\"alice@example.com\"}" 871 - ``` 872 - 873 - **Add** to an array using `-` (append): 874 - 875 - ```ocaml 876 - # let with_new_score = add (of_string "/scores/-") sample 877 - ~value:(Jsont.Json.number 95.);; 878 - val with_new_score : Jsont.json = 879 - Jsont.Object 880 - ([(("name", <abstr>), Jsont.String ("Alice", <abstr>)); 881 - (("scores", <abstr>), 882 - Jsont.Array 883 - ([Jsont.Number (85., <abstr>); Jsont.Number (92., <abstr>); 884 - Jsont.Number (78., <abstr>); Jsont.Number (95., <abstr>)], 885 - <abstr>))], 886 - <abstr>) 887 - # json_to_string with_new_score;; 888 - - : string = "{\"name\":\"Alice\",\"scores\":[85,92,78,95]}" 889 - ``` 890 - 891 - **Replace** an existing value: 892 - 893 - ```ocaml 894 - # let renamed = replace (of_string "/name") sample 895 - ~value:(Jsont.Json.string "Bob");; 896 - val renamed : Jsont.json = 897 - Jsont.Object 898 - ([(("name", <abstr>), Jsont.String ("Bob", <abstr>)); 899 - (("scores", <abstr>), 900 - Jsont.Array 901 - ([Jsont.Number (85., <abstr>); Jsont.Number (92., <abstr>); 902 - Jsont.Number (78., <abstr>)], 903 - <abstr>))], 904 - <abstr>) 905 - # json_to_string renamed;; 906 - - : string = "{\"name\":\"Bob\",\"scores\":[85,92,78]}" 907 - ``` 908 - 909 - **Remove** a value: 910 - 911 - ```ocaml 912 - # let without_first = remove (of_string "/scores/0") sample;; 913 - val without_first : Jsont.json = 914 - Jsont.Object 915 - ([(("name", <abstr>), Jsont.String ("Alice", <abstr>)); 916 - (("scores", <abstr>), 917 - Jsont.Array 918 - ([Jsont.Number (92., <abstr>); Jsont.Number (78., <abstr>)], <abstr>))], 919 - <abstr>) 920 - # json_to_string without_first;; 921 - - : string = "{\"name\":\"Alice\",\"scores\":[92,78]}" 922 783 ``` 923 784 924 785 ### Nested Path Extraction
+26 -29
src/jsont_pointer.ml
··· 56 56 57 57 (* Index type - represents how a token is interpreted in context *) 58 58 module Index = struct 59 - type t = 60 - | Mem of string 61 - | Nth of int 62 - | End 59 + type t = [ `Mem of string | `Nth of int | `End ] 63 60 64 61 let pp ppf = function 65 - | Mem s -> Format.fprintf ppf "/%s" (Token.escape s) 66 - | Nth n -> Format.fprintf ppf "/%d" n 67 - | End -> Format.fprintf ppf "/-" 62 + | `Mem s -> Format.fprintf ppf "/%s" (Token.escape s) 63 + | `Nth n -> Format.fprintf ppf "/%d" n 64 + | `End -> Format.fprintf ppf "/-" 68 65 69 66 let equal i1 i2 = match i1, i2 with 70 - | Mem s1, Mem s2 -> String.equal s1 s2 71 - | Nth n1, Nth n2 -> Int.equal n1 n2 72 - | End, End -> true 67 + | `Mem s1, `Mem s2 -> String.equal s1 s2 68 + | `Nth n1, `Nth n2 -> Int.equal n1 n2 69 + | `End, `End -> true 73 70 | _ -> false 74 71 75 72 let compare i1 i2 = match i1, i2 with 76 - | Mem s1, Mem s2 -> String.compare s1 s2 77 - | Mem _, _ -> -1 78 - | _, Mem _ -> 1 79 - | Nth n1, Nth n2 -> Int.compare n1 n2 80 - | Nth _, End -> -1 81 - | End, Nth _ -> 1 82 - | End, End -> 0 73 + | `Mem s1, `Mem s2 -> String.compare s1 s2 74 + | `Mem _, _ -> -1 75 + | _, `Mem _ -> 1 76 + | `Nth n1, `Nth n2 -> Int.compare n1 n2 77 + | `Nth _, `End -> -1 78 + | `End, `Nth _ -> 1 79 + | `End, `End -> 0 83 80 84 81 let of_path_index (idx : Jsont.Path.index) : t = 85 82 match idx with 86 - | Jsont.Path.Mem (s, _meta) -> Mem s 87 - | Jsont.Path.Nth (n, _meta) -> Nth n 83 + | Jsont.Path.Mem (s, _meta) -> `Mem s 84 + | Jsont.Path.Nth (n, _meta) -> `Nth n 88 85 89 86 let to_path_index (idx : t) : Jsont.Path.index option = 90 87 match idx with 91 - | Mem s -> Some (Jsont.Path.Mem (s, Jsont.Meta.none)) 92 - | Nth n -> Some (Jsont.Path.Nth (n, Jsont.Meta.none)) 93 - | End -> None 88 + | `Mem s -> Some (Jsont.Path.Mem (s, Jsont.Meta.none)) 89 + | `Nth n -> Some (Jsont.Path.Nth (n, Jsont.Meta.none)) 90 + | `End -> None 94 91 end 95 92 96 93 (* Internal representation: raw unescaped tokens. ··· 112 109 (* Convert to Index for a given JSON value type *) 113 110 let to_index seg ~for_array = 114 111 match seg with 115 - | End -> Index.End 112 + | End -> `End 116 113 | Token s -> 117 114 if for_array then 118 115 match Token.is_valid_array_index s with 119 - | Some n -> Index.Nth n 120 - | None -> Index.Mem s (* Invalid index becomes member for error msg *) 116 + | Some n -> `Nth n 117 + | None -> `Mem s (* Invalid index becomes member for error msg *) 121 118 else 122 - Index.Mem s 119 + `Mem s 123 120 124 121 (* Convert from Index *) 125 122 let of_index = function 126 - | Index.End -> End 127 - | Index.Mem s -> Token s 128 - | Index.Nth n -> Token (string_of_int n) 123 + | `End -> End 124 + | `Mem s -> Token s 125 + | `Nth n -> Token (string_of_int n) 129 126 end 130 127 131 128 (* Pointer type - list of segments *)
+24 -23
src/jsont_pointer.mli
··· 65 65 a numeric index or the special end-of-array marker [-]. *) 66 66 module Index : sig 67 67 68 - type t = 69 - | Mem of string 70 - (** [Mem name] indexes into an object member with the given [name]. 68 + type t = [ 69 + | `Mem of string 70 + (** [`Mem name] indexes into an object member with the given [name]. 71 71 The name is unescaped (i.e., [/] and [~] appear literally). *) 72 - | Nth of int 73 - (** [Nth n] indexes into an array at position [n] (zero-based). 72 + | `Nth of int 73 + (** [`Nth n] indexes into an array at position [n] (zero-based). 74 74 Must be non-negative and without leading zeros in string form 75 75 (except for [0] itself). *) 76 - | End 77 - (** [End] represents the [-] token, indicating the position after 76 + | `End 77 + (** [`End] represents the [-] token, indicating the position after 78 78 the last element of an array. This is used for append operations 79 79 in {!Jsont_pointer.add} and similar mutation functions. 80 - Evaluating a pointer containing [End] with {!Jsont_pointer.get} 80 + Evaluating a pointer containing [`End] with {!Jsont_pointer.get} 81 81 will raise an error since it refers to a nonexistent element. *) 82 + ] 82 83 83 84 val pp : Format.formatter -> t -> unit 84 85 (** [pp] formats an index in JSON Pointer string notation. *) ··· 96 97 97 98 val to_path_index : t -> Jsont.Path.index option 98 99 (** [to_path_index idx] converts to a {!Jsont.Path.index}. 99 - Returns [None] for {!End} since it has no equivalent in 100 + Returns [None] for [`End] since it has no equivalent in 100 101 {!Jsont.Path}. *) 101 102 end 102 103 ··· 144 145 The string must be either empty (representing the root) or start 145 146 with [/]. Each segment between [/] characters is unescaped as a 146 147 reference token. Segments that are valid non-negative integers 147 - without leading zeros become {!Index.Nth} indices; the string [-] 148 - becomes {!Index.End}; all others become {!Index.Mem}. 148 + without leading zeros become [`Nth] indices; the string [-] 149 + becomes [`End]; all others become [`Mem]. 149 150 150 151 @raise Jsont.Error if [s] has invalid syntax: 151 152 - Non-empty string not starting with [/] ··· 204 205 205 206 val to_path : t -> Jsont.Path.t option 206 207 (** [to_path p] converts to a {!Jsont.Path.t}. 207 - Returns [None] if [p] contains an {!Index.End} index. *) 208 + Returns [None] if [p] contains an [`End] index. *) 208 209 209 210 val to_path_exn : t -> Jsont.Path.t 210 211 (** [to_path_exn p] is like {!to_path} but raises {!Jsont.Error} ··· 221 222 @raise Jsont.Error if: 222 223 - The pointer references a nonexistent object member 223 224 - The pointer references an out-of-bounds array index 224 - - The pointer contains {!Index.End} (since [-] always refers 225 + - The pointer contains [`End] (since [-] always refers 225 226 to a nonexistent element) 226 - - An index type doesn't match the JSON value (e.g., {!Index.Nth} 227 + - An index type doesn't match the JSON value (e.g., [`Nth] 227 228 on an object) *) 228 229 229 230 val get_result : t -> Jsont.json -> (Jsont.json, Jsont.Error.t) result ··· 246 247 val set : t -> Jsont.json -> value:Jsont.json -> Jsont.json 247 248 (** [set p json ~value] replaces the value at pointer [p] with [value]. 248 249 249 - For {!Index.End} on arrays, appends [value] to the end of the array. 250 + For [`End] on arrays, appends [value] to the end of the array. 250 251 251 252 @raise Jsont.Error if the pointer doesn't resolve to an existing 252 - location (except for {!Index.End} on arrays). *) 253 + location (except for [`End] on arrays). *) 253 254 254 255 val add : t -> Jsont.json -> value:Jsont.json -> Jsont.json 255 256 (** [add p json ~value] adds [value] at the location specified by [p]. ··· 258 259 {ul 259 260 {- For objects: If the member exists, it is replaced. If it doesn't 260 261 exist, a new member is added.} 261 - {- For arrays with {!Index.Nth}: Inserts [value] {e before} the 262 + {- For arrays with [`Nth]: Inserts [value] {e before} the 262 263 specified index, shifting subsequent elements. The index must be 263 264 valid (0 to length inclusive).} 264 - {- For arrays with {!Index.End}: Appends [value] to the array.}} 265 + {- For arrays with [`End]: Appends [value] to the array.}} 265 266 266 267 @raise Jsont.Error if: 267 268 - The parent of the target location doesn't exist 268 - - An array index is out of bounds (except for {!Index.End}) 269 + - An array index is out of bounds (except for [`End]) 269 270 - The parent is not an object or array *) 270 271 271 272 val remove : t -> Jsont.json -> Jsont.json ··· 277 278 @raise Jsont.Error if: 278 279 - [p] is the root (cannot remove the root) 279 280 - The pointer doesn't resolve to an existing value 280 - - The pointer contains {!Index.End} *) 281 + - The pointer contains [`End] *) 281 282 282 283 val replace : t -> Jsont.json -> value:Jsont.json -> Jsont.json 283 284 (** [replace p json ~value] replaces the value at pointer [p] with [value]. ··· 286 287 287 288 @raise Jsont.Error if: 288 289 - The pointer doesn't resolve to an existing value 289 - - The pointer contains {!Index.End} *) 290 + - The pointer contains [`End] *) 290 291 291 292 val move : from:t -> path:t -> Jsont.json -> Jsont.json 292 293 (** [move ~from ~path json] moves the value from [from] to [path]. ··· 297 298 @raise Jsont.Error if: 298 299 - [from] doesn't resolve to a value 299 300 - [path] is a proper prefix of [from] (would create a cycle) 300 - - Either pointer contains {!Index.End} *) 301 + - Either pointer contains [`End] *) 301 302 302 303 val copy : from:t -> path:t -> Jsont.json -> Jsont.json 303 304 (** [copy ~from ~path json] copies the value from [from] to [path]. ··· 307 308 308 309 @raise Jsont.Error if: 309 310 - [from] doesn't resolve to a value 310 - - Either pointer contains {!Index.End} *) 311 + - Either pointer contains [`End] *) 311 312 312 313 val test : t -> Jsont.json -> expected:Jsont.json -> bool 313 314 (** [test p json ~expected] tests if the value at [p] equals [expected].
+3 -3
test/test_pointer.ml
··· 24 24 let indices = Jsont_pointer.indices p in 25 25 let index_strs = List.map (fun idx -> 26 26 match idx with 27 - | Jsont_pointer.Index.Mem s -> Printf.sprintf "Mem:%s" s 28 - | Jsont_pointer.Index.Nth n -> Printf.sprintf "Nth:%d" n 29 - | Jsont_pointer.Index.End -> "End" 27 + | `Mem s -> Printf.sprintf "Mem:%s" s 28 + | `Nth n -> Printf.sprintf "Nth:%d" n 29 + | `End -> "End" 30 30 ) indices in 31 31 Printf.printf "OK: [%s]\n" (String.concat ", " index_strs) 32 32 with Jsont.Error e ->