Created
May 8, 2024 13:52
-
-
Save adclz/be7a383580975672e413430c679d3a00 to your computer and use it in GitHub Desktop.
A serde_json parse macro that generates variables automatically.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// The `json_parse!` macro automatically generates all `serde_json` `as_` operations based on a provided JSON syntax, binding all variables to the keys. | |
/// When a key cannot be converted, Result is propagated using the `?` operator. | |
/// To handle errors before propagation, you can provide a closure as the first argument to this macro which will be called by `.map_err()`. | |
/// The data argument has to be of type `&Map<String, Value>`. | |
/// | |
/// ## Usage: | |
/// | |
/// ```rust | |
/// json_parse!( | |
/// |err| format!("Could not parse some_data, {}", err), | |
/// some_data { | |
/// field1 => as_str, // let field1: &str | |
/// field2 => as_bool, // let field2: bool | |
/// } | |
/// ); | |
/// ``` | |
/// ## Optional fields | |
/// Each field can be optional if set with '?' and will return an Option instead of a Result. | |
/// ```rust | |
/// json_parse!( | |
/// |err| format!("Could not parse some_data, {}", err), | |
/// some_data { | |
/// field1 => as_str, // Result | |
/// field2? => as_bool, // Option | |
/// } | |
/// ); | |
/// ``` | |
/// ## Optional type | |
/// If not type is present, a `&Value` will be returned. | |
/// ```rust | |
/// json_parse!( | |
/// |err| format!("Could not parse some_data, {}", err), | |
/// some_data { | |
/// field1, // Result<&Value, String> | |
/// field2?, // Option<&Value>` | |
/// } | |
/// ); | |
/// ``` | |
/// ## Nested objects | |
/// Supports nested objects but only with the last key. | |
/// ```rust | |
/// json_parse!( | |
/// |err| format!("Could not parse some_data, {}", err), | |
/// some_data { | |
/// field1 => as_str, | |
/// field2 => { | |
/// nested_field1 => as_bool, | |
/// nested_field2? => as_i64, | |
/// } | |
/// } | |
/// ); | |
/// ``` | |
#[macro_export] | |
macro_rules! json_parse { | |
// Recursive | |
(@key $map_err: expr, $json: ident { $($key:tt)* } $field: ident => { $($rest:tt)* }) => { | |
let field_str = stringify!($field); | |
let $field = match $json.contains_key(field_str) { | |
true => match $json[field_str].as_object() { | |
Some(v) => Ok(v), | |
None => Err(format!("Invalid JSON: {} is not a sub-object", field_str)), | |
}, | |
false => Err(format!("Invalid JSON: key '{}' is missing in {:?}", field_str, $json)), | |
}.map_err($map_err)?; | |
// recurse into the nested field | |
json_parse!( | |
@key $map_err, | |
$field | |
{ $($key)* } | |
$($rest)* | |
); | |
}; | |
// Required | |
(@key $map_err: expr, $json: ident { $($key:tt)* } $field: ident => $_as: ident, $($rest:tt)*) => { | |
json_parse!(@key $map_err, $json | |
{ | |
$($key)* | |
let field_str = stringify!($field); | |
let $field = match $json.contains_key(field_str) { | |
true => match $json[field_str].$_as() { | |
Some(v) => Ok(v), | |
None => Err(format!("Invalid JSON: {} is not of type {}", stringify!($field), stringify!($_as))), | |
}, | |
false => Err(format!("Invalid JSON: key '{}' is missing in {:?}", field_str, $json)), | |
}.map_err($map_err)?; | |
} | |
$($rest)*); | |
}; | |
(@key $map_err: expr, $json: ident { $($key:tt)* } $field: ident, $($rest:tt)*) => { | |
json_parse!(@key $map_err, $json | |
{ | |
$($key)* | |
let field_str = stringify!($field); | |
let $field = match $json.contains_key(field_str) { | |
true => Ok(&$json[field_str]), | |
false => Err(format!("Invalid JSON: key '{}' is missing in {:?}", field_str, $json)), | |
}.map_err($map_err)?; | |
} | |
$($rest)*); | |
}; | |
// Optional | |
(@key $map_err: expr, $json: ident { $($key:tt)* } $field: ident? => $_as: ident, $($rest:tt)*) => { | |
json_parse!(@key $map_err, $json | |
{ | |
$($key)* | |
let field_str = stringify!($field); | |
let $field = match $json.contains_key(field_str) { | |
true => match $json[field_str].$_as() { | |
Some(v) => Some(v), | |
None => None, | |
}, | |
false => None, | |
}; | |
} | |
$($rest)*); | |
}; | |
(@key $map_err: expr, $json: ident { $($key:tt)* } $field: ident?, $($rest:tt)*) => { | |
json_parse!(@key $map_err, $json | |
{ | |
$($key)* | |
let field_str = stringify!($field); | |
let $field = match $json.contains_key(field_str) { | |
true => Some(&$json[field_str]), | |
false => None, | |
}; | |
} | |
$($rest)*); | |
}; | |
(@key $map_err: expr, $json: ident { $($key:tt)* }) => { | |
$($key)* | |
}; | |
($map_err: expr, $json: ident { $($key:tt)* }) => { | |
json_parse!(@key $map_err, $json {} $($key)*); | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment