I have one special deserialize requirement, that b may be optional, and if not present it should be 8, but then finally a must never be less than b. (Error if so)
Just expanding the derive deserialize macro for this simple struct produces a wall of text, which is many lines of boilerplate I'd like to avoid, so I was wondering if anyone knows of a nice way I could inject my requirements in a minimal-code kind of way, something like deserialize_with but obviously that doesn't have to power to look at adjacent fields, unfortunately.
First off, the expanded #[derive(Deserialize)] impl (or any derived impl, in fact) is not even remotely similar to what you would write by hand. Generated code has to cover a lot of general cases and it must be bullet-proof to call from anywhere, basically, which requires a lot of careful code wrangling. If you were to manually implement Deserialize, it would be much nicer.
When implementing the deserializer manually, you can just create a proxy type, derive a deserializer for it, and validate it after the fact, like this Playground:
Edit: Misread the question first; the section below is not relevant any more.
But then the key thing to observe is that this is not really a requirement on your struct Foo. Your requirement is actually an invariant of the type of whatever its b field is. Thus, a newtype is appropriate. Just implement that one requirement for the type of the field b, and continue deriving your impls for Foo.
You can let serde do the heavy lifting how to deserialize, by using a type which matches your data model (i.e., b: Option<usize>), and do any further checks in a TryFrom.
#[derive(Debug, serde::Deserialize)]
#[serde(try_from = "FooDe")]
pub struct Foo {
a: usize,
b: usize,
}
#[derive(serde::Deserialize)]
pub struct FooDe {
a: usize,
b: Option<usize>,
}
impl TryFrom<FooDe> for Foo {
type Error = &'static str;
fn try_from(other: FooDe) -> Result<Self, Self::Error> {
let a = other.a;
let b = other.b.unwrap_or(8);
if a < b {
Err("a must never be less than b")
} else {
Ok(Foo {
a,
b
})
}
}
}
fn main() {
assert!(serde_json::from_str::<Foo>(r#"{"a": 1}"#).is_err());
assert!(serde_json::from_str::<Foo>(r#"{"a": 1, "b": 0}"#).is_ok());
dbg!(serde_json::from_str::<Foo>(r#"{"a": 16}"#).unwrap());
}