serde-rs / serde

Serialization framework for Rust
https://serde.rs/
Apache License 2.0
9.16k stars 774 forks source link

Deserialize empty string as None #1425

Closed dtolnay closed 5 years ago

dtolnay commented 5 years ago

Question from IRC:

How to deserialize empty string from json to None?

dtolnay commented 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());
}
Shadlock0133 commented 5 years ago

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()))
}
dtolnay commented 5 years ago

Nice!

florianjacob commented 5 years ago

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.

bestouff commented 5 years ago

This last comment is really precious. Could this be somehow sneaked into serde field attributes ?

mickdekkers commented 4 years ago

There's also a crate called serde_with that implements this in its string_empty_as_none de/serializer