diff --git a/serde/src/de/impls.rs b/serde/src/de/impls.rs index b13800dc..59f667ed 100644 --- a/serde/src/de/impls.rs +++ b/serde/src/de/impls.rs @@ -529,6 +529,14 @@ where { T::deserialize(deserializer).map(Some) } + + #[doc(hidden)] + fn __private_visit_untagged_option(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Ok(T::deserialize(deserializer).ok()) + } } impl<'de, T> Deserialize<'de> for Option diff --git a/serde/src/de/mod.rs b/serde/src/de/mod.rs index 826662df..fbc24016 100644 --- a/serde/src/de/mod.rs +++ b/serde/src/de/mod.rs @@ -1529,6 +1529,15 @@ pub trait Visitor<'de>: Sized { let _ = data; Err(Error::invalid_type(Unexpected::Enum, &self)) } + + // Used when deserializing a flattened Option field. Not public API. + #[doc(hidden)] + fn __private_visit_untagged_option(self, _: D) -> Result + where + D: Deserializer<'de>, + { + Err(()) + } } //////////////////////////////////////////////////////////////////////////////// diff --git a/serde/src/private/de.rs b/serde/src/private/de.rs index dcf5008f..fab85534 100644 --- a/serde/src/private/de.rs +++ b/serde/src/private/de.rs @@ -2645,10 +2645,7 @@ impl<'a, 'de, E> FlatMapDeserializer<'a, 'de, E> where E: Error, { - fn deserialize_other(self, _: V) -> Result - where - V: Visitor<'de>, - { + fn deserialize_other() -> Result { Err(Error::custom("can only flatten structs and maps")) } } @@ -2657,11 +2654,11 @@ where macro_rules! forward_to_deserialize_other { ($($func:ident ( $($arg:ty),* ))*) => { $( - fn $func(self, $(_: $arg,)* visitor: V) -> Result + fn $func(self, $(_: $arg,)* _visitor: V) -> Result where V: Visitor<'de>, { - self.deserialize_other(visitor) + Self::deserialize_other() } )* } @@ -2741,6 +2738,16 @@ where visitor.visit_newtype_struct(self) } + fn deserialize_option(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + match visitor.__private_visit_untagged_option(self) { + Ok(value) => Ok(value), + Err(()) => Self::deserialize_other(), + } + } + forward_to_deserialize_other! { deserialize_bool() deserialize_i8() @@ -2758,7 +2765,6 @@ where deserialize_string() deserialize_bytes() deserialize_byte_buf() - deserialize_option() deserialize_unit() deserialize_unit_struct(&'static str) deserialize_seq() diff --git a/serde/src/private/ser.rs b/serde/src/private/ser.rs index 54bb4ce0..c21de163 100644 --- a/serde/src/private/ser.rs +++ b/serde/src/private/ser.rs @@ -1122,14 +1122,14 @@ where } fn serialize_none(self) -> Result { - Err(self.bad_type(Unsupported::Optional)) + Ok(()) } - fn serialize_some(self, _: &T) -> Result + fn serialize_some(self, value: &T) -> Result where T: Serialize, { - Err(self.bad_type(Unsupported::Optional)) + value.serialize(self) } fn serialize_unit(self) -> Result { diff --git a/test_suite/tests/test_annotations.rs b/test_suite/tests/test_annotations.rs index 248e4211..7aaa97d5 100644 --- a/test_suite/tests/test_annotations.rs +++ b/test_suite/tests/test_annotations.rs @@ -2095,3 +2095,84 @@ fn test_flatten_untagged_enum() { ], ); } + +#[test] +fn test_flatten_option() { + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct Outer { + #[serde(flatten)] + inner1: Option, + #[serde(flatten)] + inner2: Option, + } + + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct Inner1 { + inner1: i32, + } + + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct Inner2 { + inner2: i32, + } + + assert_tokens( + &Outer { + inner1: Some(Inner1 { + inner1: 1, + }), + inner2: Some(Inner2 { + inner2: 2, + }), + }, + &[ + Token::Map { len: None }, + Token::Str("inner1"), + Token::I32(1), + Token::Str("inner2"), + Token::I32(2), + Token::MapEnd, + ], + ); + + assert_tokens( + &Outer { + inner1: Some(Inner1 { + inner1: 1, + }), + inner2: None, + }, + &[ + Token::Map { len: None }, + Token::Str("inner1"), + Token::I32(1), + Token::MapEnd, + ], + ); + + assert_tokens( + &Outer { + inner1: None, + inner2: Some(Inner2 { + inner2: 2, + }), + }, + &[ + Token::Map { len: None }, + Token::Str("inner2"), + Token::I32(2), + Token::MapEnd, + ], + ); + + assert_tokens( + &Outer { + inner1: None, + inner2: None, + }, + &[ + Token::Map { len: None }, + Token::MapEnd, + ], + ); +}