Serde flatten to Option default to None

Does anyone know a workaround that allows to default to None, when using serde(flatten)?

The problem is in my case, that serde flatten always instantiates the inner HashMap of an Option<HashMap> for the target container. Thus I cannot have a None in case there are no extra fields to flatten, but there is always an empty Some(HashMap).

#[derive(Debug, Deserialize, Serialize)]
struct MyStruct {
    my_field: String,
    #[serde(flatten, default = "none_extra")]
    extra: Option<HashMap<String, Value>>
}

fn none_extra() -> Option<HashMap<String, Value>> {
    None
}

fn main() {
    // I would like to deserialize into:
    // MyStruct {
    //     my_field: "value",
    //     extra: None,
    // }
    // But I get this:
    let no_extra: MyStruct = serde_json::from_str(r#"{ "my_field": "value" }"#).unwrap();
    dbg!(no_extra);
}

Here is the link to the playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=4c05202324c663b50f48363e9e12f173

There is already a feature request for this, but I would be interested in a workaround if anybody knows one.

I think this should work:

use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;

#[derive(Debug, Deserialize, Serialize)]
struct MyStruct {
    my_field: String,
    #[serde(flatten, deserialize_with = "none_extra")]
    extra: Option<HashMap<String, Value>>,
}

pub fn none_extra<'de, D>(deserializer: D) -> Result<Option<HashMap<String, Value>>, D::Error>
where
    D: serde::de::Deserializer<'de>,
{
    let s = HashMap::deserialize(deserializer)?;
    Ok((s.len() != 0).then_some(s))
}

fn main() {
    // I would like to deserialize into:
    // MyStruct {
    //     my_field: "value",
    //     extra: None,
    // }
    // But I get this:
    let no_extra: MyStruct = serde_json::from_str(r#"{ "my_field": "value" }"#).unwrap();
    dbg!(no_extra);

    let with_extra: MyStruct =
        serde_json::from_str(r#"{ "my_field": "value", "some_extra": 42 }"#).unwrap();
    dbg!(with_extra);
}

Playground.

1 Like