Skip to content

Instantly share code, notes, and snippets.

@adclz
Created May 8, 2024 13:52
Show Gist options
  • Save adclz/be7a383580975672e413430c679d3a00 to your computer and use it in GitHub Desktop.
Save adclz/be7a383580975672e413430c679d3a00 to your computer and use it in GitHub Desktop.
A serde_json parse macro that generates variables automatically.
/// 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