serde-rs / serde

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

Cannot deserialize Adjacently-Tagged enums when content field is missing. #2706

Open NTmatter opened 4 months ago

NTmatter commented 4 months ago

I have a struct comprised entirely of optional fields, which is represented as an Adjacently-Tagged enum. The JSON API hands back Adjacently-Tagged structures, however the Content field can be null in some cases.

As an example, the API response can resemble:

{ "type": "foo", "content": { "bar": "baz" }},
{ "type": "foo", "content": null },

Ideally, this would deserialize into:

Example::Foo { bar: Some("baz") },
Example::Foo { bar: None },

Unfortunately, the JSON Deserializer fails on the null Content field. As far as I can tell, there's no way to ignore this condition or use the default None values on Adjacently-Tagged enums.

use serde::Deserialize;
use serde_json;

#[derive(Debug, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case", tag = "t", content = "c")]
enum Example {
    Foo { bar: Option<bool> },
    Unit,
}

fn main() -> Result<(), serde_json::Error> {
    let _summary = r#"[
        { "t": "foo", "c": { "bar": "baz"} },
        { "t": "foo", "c": { "bar": null } },
        { "t": "foo", "c": null }
    ]"#;

    // When the entire object is fully-specified, bar is Some(String)
    assert_eq!(
        serde_json::from_str::<Example>(r#"{"t": "foo", "c": {"bar": true }}"#)?,
        Example::Foo {
            bar: Some(true)
        }
    );

    // Field data is None when the bar property is null.
    assert_eq!(
        serde_json::from_str::<Example>(r#"{ "t": "foo", "c": {"bar": null }}"#)?,
        Example::Foo { bar: None }
    );

    // Field data is None when the property is unspecified.
    assert_eq!(
        serde_json::from_str::<Example>(r#"{ "t": "foo", "c": {}}"#)?,
        Example::Foo { bar: None }
    );

    // Unit variants handle this situation without issue
    assert_eq!(
        serde_json::from_str::<Example>(r#"{ "t": "unit", "c": null}"#)?,
        Example::Unit
    );
    assert_eq!(
        serde_json::from_str::<Example>(r#"{ "t": "unit" }"#)?,
        Example::Unit
    );

    // Fails with an `"expected value"` error. Ideally, there would be a way to try
    // returning Defaults or None, similar to the empty-object case above. It would
    // likely be best if this behaviour could be controlled by attributes on the enum
    // and variants.
    assert_eq!(
        serde_json::from_str::<Example>(r#"{ "t": "foo", "c": null }"#)?,
        Example::Foo { bar: None },
    );

    Ok(())
}

As a workaround, I'm currently parsing the results into generic serde_json::Value and doing a sanitization pass to replace the null values with {}. The resulting tree can subsequently be parsed with serde_json::from_value, however this feels rather inelegant.