+3
-1
doc/dune
+3
-1
doc/dune
+329
-10
doc/tutorial.md
+329
-10
doc/tutorial.md
···
86
86
87
87
Each reference token becomes an `Index.t` value in the library:
88
88
89
+
<!-- $MDX skip -->
89
90
```ocaml
90
91
type t =
91
92
| Mem of string (* Object member access *)
···
120
121
121
122
In the library, this is the `Jsont_pointer.get` function:
122
123
124
+
<!-- $MDX skip -->
123
125
```ocaml
124
126
val get : t -> Jsont.json -> Jsont.json
125
127
```
···
154
156
The empty pointer returns the whole document. In OCaml, this is
155
157
`Jsont_pointer.root`:
156
158
159
+
<!-- $MDX skip -->
157
160
```ocaml
158
161
val root : t
159
162
(** The empty pointer that references the whole document. *)
···
218
221
**Important**: When using the OCaml library programmatically, you don't need
219
222
to worry about escaping. The `Index.Mem` variant holds the literal key name:
220
223
224
+
<!-- $MDX skip -->
221
225
```ocaml
222
226
(* To access the key "a/b", just use the literal string *)
223
227
let pointer = Jsont_pointer.make [Mem "a/b"]
···
280
284
281
285
The library provides both exception-raising and result-returning variants:
282
286
287
+
<!-- $MDX skip -->
283
288
```ocaml
284
289
val get : t -> Jsont.json -> Jsont.json
285
290
val get_result : t -> Jsont.json -> (Jsont.json, Jsont.Error.t) result
···
365
370
366
371
In OCaml:
367
372
373
+
<!-- $MDX skip -->
368
374
```ocaml
369
375
val add : t -> Jsont.json -> value:Jsont.json -> Jsont.json
370
376
```
···
470
476
to think about escaping. The `Index.Mem` variant stores unescaped strings,
471
477
and escaping happens automatically during serialization:
472
478
479
+
<!-- $MDX skip -->
473
480
```ocaml
474
481
(* Create a pointer to key "a/b" - no escaping needed *)
475
482
let p = Jsont_pointer.make [Mem "a/b"]
···
484
491
485
492
The `Token` module exposes the escaping functions if you need them:
486
493
494
+
<!-- $MDX skip -->
487
495
```ocaml
488
496
module Token : sig
489
497
val escape : string -> string (* "a/b" -> "a~1b" *)
···
612
620
613
621
The library provides functions for URI fragment encoding:
614
622
623
+
<!-- $MDX skip -->
615
624
```ocaml
616
625
val to_uri_fragment : t -> string
617
626
val of_uri_fragment : string -> t
···
656
665
657
666
## Jsont Integration
658
667
659
-
The library integrates with the `Jsont` codec system for typed access:
668
+
The library integrates with the `Jsont` codec system, allowing you to
669
+
combine JSON Pointer navigation with typed decoding. This is powerful
670
+
because you can point to a location in a JSON document and decode it
671
+
directly to an OCaml type.
672
+
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
+
```ocaml
694
+
# let config_json = parse_json {|{
695
+
"database": {
696
+
"host": "localhost",
697
+
"port": 5432,
698
+
"credentials": {"username": "admin", "password": "secret"}
699
+
},
700
+
"features": ["auth", "logging", "metrics"]
701
+
}|};;
702
+
val config_json : Jsont.json =
703
+
Jsont.Object
704
+
([(("database", <abstr>),
705
+
Jsont.Object
706
+
([(("host", <abstr>), Jsont.String ("localhost", <abstr>));
707
+
(("port", <abstr>), Jsont.Number (5432., <abstr>));
708
+
(("credentials", <abstr>),
709
+
Jsont.Object
710
+
([(("username", <abstr>), Jsont.String ("admin", <abstr>));
711
+
(("password", <abstr>), Jsont.String ("secret", <abstr>))],
712
+
<abstr>))],
713
+
<abstr>));
714
+
(("features", <abstr>),
715
+
Jsont.Array
716
+
([Jsont.String ("auth", <abstr>); Jsont.String ("logging", <abstr>);
717
+
Jsont.String ("metrics", <abstr>)],
718
+
<abstr>))],
719
+
<abstr>)
720
+
```
721
+
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
+
### Typed Access with `path`
794
+
795
+
The `path` combinator combines pointer navigation with typed decoding:
796
+
797
+
```ocaml
798
+
# let db_host =
799
+
Jsont.Json.decode
800
+
(path (of_string "/database/host") Jsont.string)
801
+
config_json
802
+
|> Result.get_ok;;
803
+
val db_host : string = "localhost"
804
+
# let db_port =
805
+
Jsont.Json.decode
806
+
(path (of_string "/database/port") Jsont.int)
807
+
config_json
808
+
|> Result.get_ok;;
809
+
val db_port : int = 5432
810
+
```
811
+
812
+
Extract a list of strings:
813
+
814
+
```ocaml
815
+
# let features =
816
+
Jsont.Json.decode
817
+
(path (of_string "/features") Jsont.(list string))
818
+
config_json
819
+
|> Result.get_ok;;
820
+
val features : string list = ["auth"; "logging"; "metrics"]
821
+
```
822
+
823
+
### Default Values with `~absent`
824
+
825
+
Use `~absent` to provide a default when a path doesn't exist:
826
+
827
+
```ocaml
828
+
# let timeout =
829
+
Jsont.Json.decode
830
+
(path ~absent:30 (of_string "/database/timeout") Jsont.int)
831
+
config_json
832
+
|> Result.get_ok;;
833
+
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
+
```
923
+
924
+
### Nested Path Extraction
925
+
926
+
You can extract values from deeply nested structures:
660
927
661
928
```ocaml
662
-
(* Codec for JSON Pointers as JSON strings *)
663
-
val jsont : t Jsont.t
929
+
# let org_json = parse_json {|{
930
+
"organization": {
931
+
"owner": {"name": "Alice", "email": "alice@example.com", "age": 35},
932
+
"members": [{"name": "Bob", "email": "bob@example.com", "age": 28}]
933
+
}
934
+
}|};;
935
+
val org_json : Jsont.json =
936
+
Jsont.Object
937
+
([(("organization", <abstr>),
938
+
Jsont.Object
939
+
([(("owner", <abstr>),
940
+
Jsont.Object
941
+
([(("name", <abstr>), Jsont.String ("Alice", <abstr>));
942
+
(("email", <abstr>),
943
+
Jsont.String ("alice@example.com", <abstr>));
944
+
(("age", <abstr>), Jsont.Number (35., <abstr>))],
945
+
<abstr>));
946
+
(("members", <abstr>),
947
+
Jsont.Array
948
+
([Jsont.Object
949
+
([(("name", <abstr>), Jsont.String ("Bob", <abstr>));
950
+
(("email", <abstr>),
951
+
Jsont.String ("bob@example.com", <abstr>));
952
+
(("age", <abstr>), Jsont.Number (28., <abstr>))],
953
+
<abstr>)],
954
+
<abstr>))],
955
+
<abstr>))],
956
+
<abstr>)
957
+
# Jsont.Json.decode
958
+
(path (of_string "/organization/owner/name") Jsont.string)
959
+
org_json
960
+
|> Result.get_ok;;
961
+
- : string = "Alice"
962
+
# Jsont.Json.decode
963
+
(path (of_string "/organization/members/0/age") Jsont.int)
964
+
org_json
965
+
|> Result.get_ok;;
966
+
- : int = 28
967
+
```
664
968
665
-
(* Query combinators *)
666
-
val path : ?absent:'a -> t -> 'a Jsont.t -> 'a Jsont.t
667
-
val set_path : ?allow_absent:bool -> 'a Jsont.t -> t -> 'a -> Jsont.json Jsont.t
668
-
val update_path : ?absent:'a -> t -> 'a Jsont.t -> Jsont.json Jsont.t
669
-
val delete_path : ?allow_absent:bool -> t -> Jsont.json Jsont.t
969
+
### Comparison: Raw vs Typed Access
970
+
971
+
**Raw access** requires pattern matching:
972
+
973
+
```ocaml
974
+
# let raw_port =
975
+
match get (of_string "/database/port") config_json with
976
+
| Jsont.Number (f, _) -> int_of_float f
977
+
| _ -> failwith "expected number";;
978
+
val raw_port : int = 5432
670
979
```
671
980
672
-
These allow you to use JSON Pointers with typed codecs rather than raw
673
-
`Jsont.json` values.
981
+
**Typed access** is cleaner and type-safe:
982
+
983
+
```ocaml
984
+
# let typed_port =
985
+
Jsont.Json.decode
986
+
(path (of_string "/database/port") Jsont.int)
987
+
config_json
988
+
|> Result.get_ok;;
989
+
val typed_port : int = 5432
990
+
```
991
+
992
+
The typed approach catches mismatches at decode time with clear errors.
674
993
675
994
## Summary
676
995