Closed dtolnay closed 5 years ago
You could provide a deserialize_with function that translates empty string to None
and non-empty string to Some(s)
:
#[macro_use]
extern crate serde_derive;
extern crate serde;
extern crate serde_json;
use serde::{Deserialize, Deserializer};
#[derive(Deserialize, Debug)]
struct S {
#[serde(deserialize_with = "empty_string_is_none")]
field: Option<String>,
}
fn empty_string_is_none<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
if s.is_empty() {
Ok(None)
} else {
Ok(Some(s))
}
}
fn main() {
let j = r#" {"field":""} "#;
println!("{:?}", serde_json::from_str::<S>(j).unwrap());
let j = r#" {"field":"x"} "#;
println!("{:?}", serde_json::from_str::<S>(j).unwrap());
}
To handle missing field had to change function to:
fn non_empty_str<'de, D: Deserializer<'de>>(d: D) -> Result<Option<String>, D::Error> {
use serde::Deserialize;
let o: Option<String> = Option::deserialize(d)?;
Ok(o.filter(|s| !s.is_empty()))
}
Nice!
This can also be made generic for Some<T>
with deserializable T
, like this:
use serde::de::IntoDeserializer;
fn empty_string_as_none<'de, D, T>(de: D) -> Result<Option<T>, D::Error>
where
D: serde::Deserializer<'de>,
T: serde::Deserialize<'de>,
{
let opt = Option::<String>::deserialize(de)?;
let opt = opt.as_ref().map(String::as_str);
match opt {
None | Some("") => Ok(None),
Some(s) => T::deserialize(s.into_deserializer()).map(Some)
}
}
(for T=String though, this calls deserialization twice)
This got developed as part of https://github.com/ruma/ruma-events/pull/25.
This last comment is really precious. Could this be somehow sneaked into serde field attributes ?
There's also a crate called serde_with that implements this in its string_empty_as_none
de/serializer
Question from IRC: