1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
/// This macro reduces boilerplate when using serde_json::Value variants when trying to get into a nested property.
///
///
/// ```
/// use std::fs;
/// // Include the crates serde_json and serde
/// use serde_json;
/// use serde;
///
///
/// let json_parsed = serde_json::json!({
/// "brand": {
/// "tesla": {
/// "model": {
/// "designers": ["Mr Bean","Elon Mosk"]
/// }
/// }
/// }
/// });
///
/// let mut a: Vec<String> = vec![];
/// let mut b: Vec<String>= vec![];
///
/// // Standard way
/// if let serde_json::Value::Object(brand) = &json_parsed {
/// let brand = brand.get("brand").unwrap();
/// if let serde_json::Value::Object(tesla) = &brand {
/// let tesla = tesla.get("tesla").unwrap();
/// if let serde_json::Value::Object(model) = &tesla {
/// let model = model.get("model").unwrap();
/// if let serde_json::Value::Object(designers) = &model {
/// let res = designers.get("designers");
/// a = serde_json::from_value::<Vec<String>>(res.unwrap().to_owned()).unwrap();
/// }
/// }
/// }
/// }
///
/// // With the macro
/// b = json_extract::json_extract!("brand.tesla.model.designers", &json_parsed, Vec<String>).unwrap();
///
///
/// assert_eq!(a,b);
/// ```
/// ## Macro args
///
/// The macro accepts 3 arguments:
///
/// 1. A &str containg the path, separated by "."
/// 2. The serde_json::Value variable to read.
/// 3. The type of the property we want to get.
///
/// ## Types supported
/// `json_serde::Value` has the following variants:
///
/// - Array
/// - Bool
/// - Null
/// - Number
/// - Object
/// - String
///
/// The third parameter to pass in the macro is a Rust type, so, things we can pass if we want to get data from some variants:
///
/// | Value variant | Rust types |
/// | ------ | ------ |
/// | Array | ``` Vec<String>, Vec<bool>, Vec<f64>, Vec<Value> ``` ... |
/// | Bool | ``` bool ``` |
/// | Number | ``` u32, i32, i64, f32, usize ``` ... |
/// | Object | ``` Value ``` |
/// | String | ``` String ``` |
/// | Null | not supported |
#[macro_export]
macro_rules! json_extract {
($keys:expr,$json:expr,$t:ty) => {{
fn get_value<'a>(prev_key: Option<&'a serde_json::Value>, key: &'a str) -> Option<&'a serde_json::Value> {
if let Some(serde_json::Value::Object(actual_obj)) = prev_key {
let val: Option<&serde_json::Value> = actual_obj.get(key);
if val.is_some() {
return val;
}
None
} else {
None
}
}
fn get_final_value<T>(prev_key: Option<&serde_json::Value>, key: &str) -> Option<T>
where
T: serde::de::DeserializeOwned + std::fmt::Debug,
{
if let Some(serde_json::Value::Object(val_return)) = prev_key {
let val_return: serde_json::Value = val_return.get(key).unwrap().clone();
let val: Result<T, serde_json::Error> = serde_json::from_value(val_return);
if val.is_ok() {
return Some(val.unwrap());
}
None
} else {
None
}
}
fn json_loop<'a, T>(chain: Vec<&'a str>, json: &'a serde_json::Value) -> Option<T>
where
T: serde::de::DeserializeOwned + std::fmt::Debug,
{
let mut prev_key: Option<&'a serde_json::Value> = None;
let mut counter: usize = 0;
let mut res: Option<T> = None;
while counter < chain.len() {
let key: &str = chain[counter];
if counter == chain.len() - 1 {
res = get_final_value::<T>(prev_key, key);
break;
}
if prev_key.is_none() {
prev_key = get_value(Some(json), key);
} else {
prev_key = get_value(prev_key, key);
}
counter += 1;
}
res
}
let chain: Vec<&str> = $keys.split(".").collect();
json_loop::<$t>(chain, $json)
}};
}