Issue with serde flatten and optional member structs

⚓ Rust    📅 2026-02-12    👤 surdeus    👁️ 1      

surdeus

Short description and MRE: when using #[serde(flatten)] on an optional member struct with nothing but optional members deserializes as a Some(Member { a: None }).

use serde::Deserialize;
use serde_with::skip_serializing_none;

#[skip_serializing_none]
#[derive(Clone, Debug, Default, Deserialize)]
pub struct TopLevel {
    #[serde(flatten)]
    pub contained: Option<Contained>,
}

#[skip_serializing_none]
#[derive(Clone, Debug, Default, Deserialize)]
pub struct Contained {
    pub optional: Option<String>,
}

fn main() {
    let json = r#"{}"#;
    let top: TopLevel = serde_json::from_str(&json).unwrap();
    println!("{top:?}");
    assert!(top.contained.is_none());
}

From the println!: TopLevel { contained: Some(Contained { optional: None }) }

Is there anything I can do to coax it into making it collapse that to a None?

Long description: I'm in the rather chaotic world of JOSE/JWT using member structs tagged with #[serde(flatten)] to be able to reuse rather than copy a lot of fields, usually with a type parameter for the field. Sometimes the collection of fields is rather large, so I wanted to tag them as optional, but that doesn't seem to work when I use flatten.

If there's a better technique I should be using, I'm all ears.

1 post - 1 participant

Read full topic

🏷️ Rust_feed