From 60e70e70988330d918c3b1a3473507efe87d994d Mon Sep 17 00:00:00 2001 From: Cuihtlauac ALVARADO Date: Fri, 28 Oct 2022 10:06:16 +0200 Subject: [PATCH 1/3] Add path function Allows recurisve access to nested object fields using a list of keys --- lib/util.ml | 2 ++ lib/util.mli | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/lib/util.ml b/lib/util.ml index 695ffb0d..ef632cf1 100644 --- a/lib/util.ml +++ b/lib/util.ml @@ -28,6 +28,8 @@ let member name = function | `Assoc obj -> assoc name obj | js -> typerr ("Can't get member '" ^ name ^ "' of non-object type ") js +let path l obj= List.fold_left (Fun.flip member) obj l + let index i = function | `List l as js -> let len = List.length l in diff --git a/lib/util.mli b/lib/util.mli index aa72f462..a4c05923 100644 --- a/lib/util.mli +++ b/lib/util.mli @@ -85,6 +85,13 @@ val member : string -> t -> t object [obj], or [`Null] if [k] is not present in [obj]. @raise Type_error if [obj] is not a JSON object. *) +val path : string list -> t -> t +(* [path l obj] returns the value associated with the list of keys [l] in + the JSON object [obj], or [`Null] if [l] did no led to an object in + [obj]. [path [k1; ...; kn] obj] is the same as + [member kn (... (member k1 obj))] + @raise Type_error if [obj] is not a JSON object. *) + val index : int -> t -> t (** [index i arr] returns the value at index [i] in the JSON array [arr]. Negative indices count from the end of the list (so -1 is the last From 91c6349ae292b4b2cd1ff176a5bcff0a5b5c76fd Mon Sep 17 00:00:00 2001 From: Marek Kubica Date: Tue, 18 Apr 2023 10:29:49 +0200 Subject: [PATCH 2/3] Make the path function use `option` --- lib/util.ml | 11 ++++++++++- lib/util.mli | 9 +++------ test/fixtures.ml | 4 +++- test/test.ml | 1 + test/test_util.ml | 22 ++++++++++++++++++++++ test/test_util.mli | 1 + 6 files changed, 40 insertions(+), 8 deletions(-) create mode 100644 test/test_util.ml create mode 100644 test/test_util.mli diff --git a/lib/util.ml b/lib/util.ml index ef632cf1..391053bf 100644 --- a/lib/util.ml +++ b/lib/util.ml @@ -28,7 +28,16 @@ let member name = function | `Assoc obj -> assoc name obj | js -> typerr ("Can't get member '" ^ name ^ "' of non-object type ") js -let path l obj= List.fold_left (Fun.flip member) obj l +let rec path l obj = + match l with + | [] -> Some obj + | key :: l -> ( + match obj with + | `Assoc assoc -> ( + match List.assoc key assoc with + | obj -> path l obj + | exception Not_found -> None) + | _ -> None) let index i = function | `List l as js -> diff --git a/lib/util.mli b/lib/util.mli index a4c05923..bfbf445c 100644 --- a/lib/util.mli +++ b/lib/util.mli @@ -85,12 +85,9 @@ val member : string -> t -> t object [obj], or [`Null] if [k] is not present in [obj]. @raise Type_error if [obj] is not a JSON object. *) -val path : string list -> t -> t -(* [path l obj] returns the value associated with the list of keys [l] in - the JSON object [obj], or [`Null] if [l] did no led to an object in - [obj]. [path [k1; ...; kn] obj] is the same as - [member kn (... (member k1 obj))] - @raise Type_error if [obj] is not a JSON object. *) +val path : string list -> t -> t option +(* [path l obj] recurses the JSON object [obj] for each key in the path + [l] until the path is empty or there is no such key in the chain. *) val index : int -> t -> t (** [index i arr] returns the value at index [i] in the JSON array [arr]. diff --git a/test/fixtures.ml b/test/fixtures.ml index 956bcff9..d90208ea 100644 --- a/test/fixtures.ml +++ b/test/fixtures.ml @@ -8,12 +8,14 @@ let json_value = ("float", `Float 0.); ("string", `String "string"); ("list", `List [ `Int 0; `Int 1; `Int 2 ]); + ("assoc", `Assoc [ ("value", `Int 42) ]); ] let json_string = "{" ^ {|"null":null,|} ^ {|"bool":true,|} ^ {|"int":0,|} ^ {|"intlit":10000000000000000000,|} ^ {|"float":0.0,|} - ^ {|"string":"string",|} ^ {|"list":[0,1,2]|} ^ "}" + ^ {|"string":"string",|} ^ {|"list":[0,1,2],|} ^ {|"assoc":{"value":42}|} + ^ "}" let unquoted_json = {|{foo: null}|} let unquoted_value = `Assoc [ ("foo", `Null) ] diff --git a/test/test.ml b/test/test.ml index 501cf8e1..92b7c92f 100644 --- a/test/test.ml +++ b/test/test.ml @@ -4,4 +4,5 @@ let () = ("equality", Test_monomorphic.equality); ("read", Test_read.single_json); ("write", Test_write.single_json); + ("util", Test_util.tests); ] diff --git a/test/test_util.ml b/test/test_util.ml new file mode 100644 index 00000000..7105701d --- /dev/null +++ b/test/test_util.ml @@ -0,0 +1,22 @@ +let path_empty () = + Alcotest.(check (option Testable.yojson)) + __LOC__ (Some Fixtures.json_value) + (Yojson.Safe.Util.path [] Fixtures.json_value) + +let path_missing () = + Alcotest.(check (option Testable.yojson)) + __LOC__ None + (Yojson.Safe.Util.path [ "does not exist" ] Fixtures.json_value) + +let path_traverse () = + Alcotest.(check (option Testable.yojson)) + __LOC__ + (Some (`Int 42)) + (Yojson.Safe.Util.path [ "assoc"; "value" ] Fixtures.json_value) + +let tests = + [ + ("empty path", `Quick, path_empty); + ("non-existing path", `Quick, path_missing); + ("traversal", `Quick, path_traverse); + ] diff --git a/test/test_util.mli b/test/test_util.mli new file mode 100644 index 00000000..d38ba9a9 --- /dev/null +++ b/test/test_util.mli @@ -0,0 +1 @@ +val tests : unit Alcotest.test_case list From 39877b4000fbc81dcec89cd7056df7d25582b61c Mon Sep 17 00:00:00 2001 From: Marek Kubica Date: Tue, 18 Apr 2023 10:31:42 +0200 Subject: [PATCH 3/3] Add change log entry --- CHANGES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index f1745112..8df5e834 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,9 @@ - Add Yojson.Raw.Util module to provide combinators for extracting fields from Yojson.Raw values. (@tmcgilchrist, #163) +- Add `Util.path` function to recurse into an object through a list of keys. + (@cuihtlauac, @Leonidas-from-XIV, #157) + ### Removed ### Changed