Skip to content

Instantly share code, notes, and snippets.

@AnthonyMikh
Created September 2, 2021 21:11
Show Gist options
  • Save AnthonyMikh/667929e30fddfe08026305d91a60bbe5 to your computer and use it in GitHub Desktop.
Save AnthonyMikh/667929e30fddfe08026305d91a60bbe5 to your computer and use it in GitHub Desktop.
Extracting arbitrary field sets from JSON
use serde::Deserialize;
struct HNil;
impl<'de> Deserialize<'de> for HNil {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_any(serde::de::IgnoredAny)?;
Ok(Self)
}
}
#[derive(Deserialize)]
struct HCons<H, T> {
#[serde(flatten)]
head: H,
#[serde(flatten)]
tail: T,
}
macro_rules! HList {
() => { HNil };
($head:ty $(, $rest:ty)* $(,)?) => { HCons<$head, HList![$($rest),*]> };
}
macro_rules! hlist_pat {
() => { HNil };
($head:pat $(, $rest:pat)* $(,)?) => { HCons { head: $head, tail: hlist_pat!($($rest),*) } };
}
#[derive(Deserialize)]
struct Items {
items: Vec<String>,
}
#[derive(Deserialize)]
struct Version {
version: u32,
}
#[derive(Deserialize)]
struct User {
user_id: String,
}
fn main() {
let json = r#"{
"user_id": "john_doe",
"items": ["salad", "juice", "beer", "fork"],
"version": 0,
"bogus": [null, 0.3, 4, "nope", {}]
}"#;
use serde_json::from_str as from_json;
let hlist_pat!(user, items, version) = from_json::<HList![User, Items, Version]>(json).unwrap();
assert_eq!(user.user_id, "john_doe");
assert_eq!(items.items, ["salad", "juice", "beer", "fork"]);
assert_eq!(version.version, 0);
let json = r#"{
"user_id": "john_doe",
"items": ["salad", "juice", "beer", "fork"],
"bogus": [null, 0.3, 4, "nope", {}]
}"#;
assert!(from_json::<HList![User, Items, Version]>(json).is_err());
assert!(from_json::<HList![User, Items]>(json).is_ok());
let json = r#"{
"version": 0,
"bogus": [null, 0.3, 4, "nope", {}]
}"#;
assert!(from_json::<HList![User, Items, Version]>(json).is_err());
assert!(from_json::<HList![Option<User>, Option<Items>, Version]>(json).is_ok());
type Rest = serde_json::Map<String, serde_json::Value>;
let json = r#"{
"user_id": "john_doe",
"items": ["salad", "juice", "beer", "fork"],
"version": 0,
"bogus": [null, 0.3, 4, "nope", {}]
}"#;
let hlist_pat!(_, _, _, rest) = from_json::<HList![User, Items, Version, Rest]>(json).unwrap();
assert_eq!(
serde_json::Value::Object(rest),
serde_json::json!({
"bogus": [null, 0.3, 4, "nope", {}],
})
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment