Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flattened fields with shared inner fields don't deserialize properly #2719

Open
CryZe opened this issue Mar 25, 2024 · 0 comments
Open

Flattened fields with shared inner fields don't deserialize properly #2719

CryZe opened this issue Mar 25, 2024 · 0 comments

Comments

@CryZe
Copy link

CryZe commented Mar 25, 2024

If I have an outer struct that has two optional inner structs that are flattened, then the second one doesn't deserialize properly if it shares any fields with the struct of the field before it.

use serde_derive::Deserialize;

fn main() {
    let json = r#"{
        "shared": "foo",
        "unique_b": "bar"
    }"#;
    let b: B = serde_json::from_str(json).unwrap();
    let outer: Outer = serde_json::from_str(json).unwrap();
    dbg!(b);
    dbg!(outer);
}

#[derive(Debug, Deserialize)]
struct Outer {
    #[serde(flatten)]
    a: Option<A>,
    #[serde(flatten)]
    b: Option<B>,
}

#[derive(Debug, Deserialize)]
struct A {
    shared: String,
    unique_a: String,
}

#[derive(Debug, Deserialize)]
struct B {
    shared: String,
    unique_b: String,
}

So in the example they both share the shared field. The problem comes from the code that's generated for the Outer type's visit_map implementation:

let mut __collect = _serde::__private::Vec::<
   _serde::__private::Option<(
       _serde::__private::de::Content,
       _serde::__private::de::Content,
   )>,
>::new();
while let _serde::__private::Some(__key) =
   _serde::de::MapAccess::next_key::<__Field>(&mut __map)?
{
   match __key {
       __Field::__other(__name) => {
           __collect.push(_serde::__private::Some((
               __name,
               _serde::de::MapAccess::next_value(&mut __map)?,
           )));
       }
   }
}
let __field0: Option<A> = _serde::de::Deserialize::deserialize(
   _serde::__private::de::FlatMapDeserializer(
       &mut __collect,
       _serde::__private::PhantomData,
   ),
)?;
let __field1: Option<B> = _serde::de::Deserialize::deserialize(
   _serde::__private::de::FlatMapDeserializer(
       &mut __collect,
       _serde::__private::PhantomData,
   ),
)?;
_serde::__private::Ok(Outer {
   a: __field0,
   b: __field1,
})

The problem is that there's an intermediate __collect buffer that partially gets consumed by the attempt to deserialize A (in particular the shared field). This makes it impossible for B to deserialize properly.

The content being taken out of the buffer happens at the end here:

serde/serde/src/private/de.rs

Lines 2830 to 2847 in 3bfab6e

fn flat_map_take_entry<'de>(
entry: &mut Option<(Content<'de>, Content<'de>)>,
recognized: &[&str],
) -> Option<(Content<'de>, Content<'de>)> {
// Entries in the FlatMapDeserializer buffer are nulled out as they get
// claimed for deserialization. We only use an entry if it is still present
// and if the field is one recognized by the current data structure.
let is_recognized = match entry {
None => false,
Some((k, _v)) => k.as_str().map_or(false, |name| recognized.contains(&name)),
};
if is_recognized {
entry.take()
} else {
None
}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

1 participant