// 957: JSON Query by Path
// get(["users", "0", "name"], json) โ Option<&JsonValue>
// Rust uses lifetime-annotated references; OCaml returns values directly
#[derive(Debug, Clone, PartialEq)]
pub enum JsonValue {
Null,
Bool(bool),
Number(f64),
Str(String),
Array(Vec<JsonValue>),
Object(Vec<(String, JsonValue)>),
}
// Approach 1: Path query returning Option<&JsonValue> (borrows from source)
pub fn get<'a>(path: &[&str], json: &'a JsonValue) -> Option<&'a JsonValue> {
match path {
[] => Some(json),
[key, rest @ ..] => match json {
JsonValue::Object(pairs) => {
let found = pairs.iter().find(|(k, _)| k == key);
found.and_then(|(_, v)| get(rest, v))
}
JsonValue::Array(items) => {
let idx: usize = key.parse().ok()?;
items.get(idx).and_then(|v| get(rest, v))
}
_ => None,
},
}
}
// Approach 2: Typed extractors (return borrowed inner values)
pub fn get_string<'a>(path: &[&str], json: &'a JsonValue) -> Option<&'a str> {
match get(path, json) {
Some(JsonValue::Str(s)) => Some(s.as_str()),
_ => None,
}
}
pub fn get_number(path: &[&str], json: &JsonValue) -> Option<f64> {
match get(path, json) {
Some(JsonValue::Number(n)) => Some(*n),
_ => None,
}
}
pub fn get_bool(path: &[&str], json: &JsonValue) -> Option<bool> {
match get(path, json) {
Some(JsonValue::Bool(b)) => Some(*b),
_ => None,
}
}
pub fn get_array<'a>(path: &[&str], json: &'a JsonValue) -> Option<&'a Vec<JsonValue>> {
match get(path, json) {
Some(JsonValue::Array(items)) => Some(items),
_ => None,
}
}
// Approach 3: Query with default (clones for ownership)
pub fn get_or(default: JsonValue, path: &[&str], json: &JsonValue) -> JsonValue {
match get(path, json) {
Some(v) => v.clone(),
None => default,
}
}
fn main() {
let json = JsonValue::Object(vec![
(
"users".to_string(),
JsonValue::Array(vec![
JsonValue::Object(vec![
("name".to_string(), JsonValue::Str("Alice".to_string())),
("age".to_string(), JsonValue::Number(30.0)),
("active".to_string(), JsonValue::Bool(true)),
]),
JsonValue::Object(vec![
("name".to_string(), JsonValue::Str("Bob".to_string())),
("age".to_string(), JsonValue::Number(25.0)),
("active".to_string(), JsonValue::Bool(false)),
]),
]),
),
("count".to_string(), JsonValue::Number(2.0)),
(
"meta".to_string(),
JsonValue::Object(vec![
("version".to_string(), JsonValue::Str("1.0".to_string())),
("tag".to_string(), JsonValue::Null),
]),
),
]);
println!("count: {:?}", get(&["count"], &json));
println!("users[0].name: {:?}", get_string(&["users", "0", "name"], &json));
println!("users[1].age: {:?}", get_number(&["users", "1", "age"], &json));
println!("missing: {:?}", get(&["missing"], &json));
}
#[cfg(test)]
mod tests {
use super::*;
fn make_json() -> JsonValue {
JsonValue::Object(vec![
(
"users".to_string(),
JsonValue::Array(vec![
JsonValue::Object(vec![
("name".to_string(), JsonValue::Str("Alice".to_string())),
("age".to_string(), JsonValue::Number(30.0)),
("active".to_string(), JsonValue::Bool(true)),
]),
JsonValue::Object(vec![
("name".to_string(), JsonValue::Str("Bob".to_string())),
("age".to_string(), JsonValue::Number(25.0)),
("active".to_string(), JsonValue::Bool(false)),
]),
]),
),
("count".to_string(), JsonValue::Number(2.0)),
(
"meta".to_string(),
JsonValue::Object(vec![
("version".to_string(), JsonValue::Str("1.0".to_string())),
("tag".to_string(), JsonValue::Null),
]),
),
])
}
#[test]
fn test_basic_queries() {
let json = make_json();
assert_eq!(get(&["count"], &json), Some(&JsonValue::Number(2.0)));
assert_eq!(
get(&["users", "0", "name"], &json),
Some(&JsonValue::Str("Alice".to_string()))
);
assert_eq!(
get(&["users", "1", "name"], &json),
Some(&JsonValue::Str("Bob".to_string()))
);
assert_eq!(get(&["meta", "tag"], &json), Some(&JsonValue::Null));
}
#[test]
fn test_missing_paths() {
let json = make_json();
assert_eq!(get(&["missing"], &json), None);
assert_eq!(get(&["users", "5", "name"], &json), None);
assert_eq!(get(&["users", "0", "missing"], &json), None);
}
#[test]
fn test_typed_extractors() {
let json = make_json();
assert_eq!(get_string(&["users", "0", "name"], &json), Some("Alice"));
assert_eq!(get_number(&["count"], &json), Some(2.0));
assert_eq!(get_bool(&["users", "0", "active"], &json), Some(true));
assert_eq!(get_bool(&["users", "1", "active"], &json), Some(false));
}
#[test]
fn test_empty_path_returns_root() {
let json = make_json();
assert_eq!(get(&[], &json), Some(&json));
}
#[test]
fn test_get_or_default() {
let json = make_json();
let result = get_or(JsonValue::Str("default".into()), &["missing"], &json);
assert_eq!(result, JsonValue::Str("default".to_string()));
let result2 = get_or(JsonValue::Null, &["count"], &json);
assert_eq!(result2, JsonValue::Number(2.0));
}
}
(* 957: JSON Query by Path *)
type json =
| Null
| Bool of bool
| Number of float
| Str of string
| Array of json list
| Object of (string * json) list
(* Approach 1: Path query returning Option *)
let rec get path json =
match path, json with
| [], j -> Some j
| key :: rest, Object pairs ->
(match List.assoc_opt key pairs with
| Some v -> get rest v
| None -> None)
| idx :: rest, Array items ->
(match int_of_string_opt idx with
| Some i when i >= 0 && i < List.length items ->
get rest (List.nth items i)
| _ -> None)
| _ -> None
(* Approach 2: Extract typed values *)
let get_string path json =
match get path json with
| Some (Str s) -> Some s
| _ -> None
let get_number path json =
match get path json with
| Some (Number n) -> Some n
| _ -> None
let get_bool path json =
match get path json with
| Some (Bool b) -> Some b
| _ -> None
let get_array path json =
match get path json with
| Some (Array items) -> Some items
| _ -> None
(* Approach 3: Query with default *)
let get_or default path json =
match get path json with
| Some v -> v
| None -> default
let () =
let json = Object [
("users", Array [
Object [("name", Str "Alice"); ("age", Number 30.0); ("active", Bool true)];
Object [("name", Str "Bob"); ("age", Number 25.0); ("active", Bool false)];
]);
("count", Number 2.0);
("meta", Object [("version", Str "1.0"); ("tag", Null)]);
] in
(* Basic path queries *)
assert (get ["count"] json = Some (Number 2.0));
assert (get ["users"; "0"; "name"] json = Some (Str "Alice"));
assert (get ["users"; "1"; "name"] json = Some (Str "Bob"));
assert (get ["users"; "0"; "active"] json = Some (Bool true));
assert (get ["users"; "1"; "active"] json = Some (Bool false));
assert (get ["meta"; "version"] json = Some (Str "1.0"));
assert (get ["meta"; "tag"] json = Some Null);
(* Missing paths return None *)
assert (get ["missing"] json = None);
assert (get ["users"; "5"; "name"] json = None);
assert (get ["users"; "0"; "missing"] json = None);
(* Typed extractors *)
assert (get_string ["users"; "0"; "name"] json = Some "Alice");
assert (get_number ["count"] json = Some 2.0);
assert (get_bool ["users"; "0"; "active"] json = Some true);
(* Empty path returns whole document *)
assert (get [] json = Some json);
Printf.printf "โ All tests passed\n"