Search code examples
serializationrustdeserializationserde

How to properly handle empty, null and valid JSON?


I need to deserialize a JSON file into either None or Some(T) in Rust. The source we are using will provide null or empty, '{}', JSON fields when no values are present. I want to handle both as the None case and only deserialize when the JSON field is not null or empty.

input: {"test": null} -> output: {"test": None}
input: {"test": {}} -> output: {"test": None}
input: {"test": {"valid_json": 42}} -> output: {"test": {"valid_json": 42}}

All of the answers I could find address one case or another but not both.


Solution

  • use serde::{Deserialize, Deserializer};
    
    #[derive(Deserialize, Debug, PartialEq)]
    struct Foo {
        #[serde(deserialize_with = "object_empty_as_none")]
        bar: Option<Bar>,
    }
    
    #[derive(Deserialize, Debug, PartialEq)]
    struct Bar {
        inner: u32,
    }
    
    pub fn object_empty_as_none<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
    where
        D: Deserializer<'de>,
        for<'a> T: Deserialize<'a>,
    {
        #[derive(Deserialize, Debug)]
        #[serde(deny_unknown_fields)]
        struct Empty {}
    
        #[derive(Deserialize, Debug)]
        #[serde(untagged)]
        enum Aux<T> {
            T(T),
            Empty(Empty),
            Null,
        }
    
        match Deserialize::deserialize(deserializer)? {
            Aux::T(t) => Ok(Some(t)),
            Aux::Empty(_) | Aux::Null => Ok(None),
        }
    }
    
    fn main() {
        let data = r#"{"bar": null}"#;
        let v: Foo = serde_json::from_str(data).unwrap();
        assert_eq!(v, Foo { bar: None });
    
        let data = r#"{"bar": {}}"#;
        let v: Foo = serde_json::from_str(data).unwrap();
        assert_eq!(v, Foo { bar: None });
    
        let data = r#"{"bar": {"inner": 42}}"#;
        let v: Foo = serde_json::from_str(data).unwrap();
        assert_eq!(
            v,
            Foo {
                bar: Some(Bar { inner: 42 })
            }
        );
    
        let data = r#"{"bar": {"not_inner": 42}}"#;
        let v: Result<Foo, _> = serde_json::from_str(data);
        assert!(v.is_err());
    }
    

    Should be enough for most case. Remove #[serde(deny_unknown_fields)] on Empty if you want to.