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

Structs with nested flattens cannot be deserialized if deny_unknown_fields is set #1547

Open
sebastianblunt opened this issue Jun 4, 2019 · 2 comments

Comments

@sebastianblunt
Copy link

Minimal repro, returns

thread 'main' panicked at 'called Result::unwrap() on an Err value: Error("unknown field c", line: 2, column: 24)', src/libcore/result.rs:999:5

Cargo.toml:

[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"

main.rs

use serde::Deserialize;

fn main() {
    let x = r#"
    {"c": "Some string"}
    "#;

    let x: A = serde_json::from_str(x).unwrap();

    eprintln!("{:?}", x);
}

#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)] // Remove this line and it works
struct A {
    #[serde(flatten)]
    a: B,
}

#[derive(Debug, Deserialize)]
struct B {
    #[serde(flatten)]
    b: C,
}

#[derive(Debug, Deserialize)]
struct C {
    c: String,
}

Removing the #[serde(deny_unknown_fields)] line causes it to work.

Possibly related to #1358?

@Kestrer
Copy link

Kestrer commented Oct 8, 2020

The cause for this is that the way #[serde(flatten)] works is that it deserializes the container as a map instead of a struct, and holds a map of unknown entries as it goes through. Then at the end it uses the field's Deserialize implementation to deserialize from said map.

Now, if the flattened type then deserializes with a struct, Serde knows the fields that the type needs. It supplies the type with only those fields, and it removes them from the map of unknown entries. However, if the type deserializes as map, Serde does not know the fields the type needs and so it gives all the fields to it, but since it doesn't know whether the type has used all the fields it has given it, it doesn't remove those entries from the map. And as I said above, using #[serde(flatten)] in a field causes it to be deserialized as a map.

As a side effect, this does work, but shouldn't:

#[derive(Deserialize)]
struct AInner {
    a: i32,
}
#[derive(Deserialize)]
struct A {
    #[serde(flatten)]
    inner: AInner,
}

#[derive(Deserialize)]
struct ShouldntDeserialize {
    a: i32,
}

#[derive(Deserialize)]
struct Container {
    #[serde(flatten)]
    a: A,
    #[serde(flatten)]
    shouldnt_deserialize: ShouldntDeserialize,
}

AInner should in theory "consume" a and not allow it to be readable after that, but since A is deserialized as a map it can't and so ShouldntDeserialize is able to access a.

Anyway back to the original example, Serde's deserialized most of A and just needs to run the deny_unknown_fields check. So are there any extra fields in the unknown fields map? Yes, there is: c, since it wasn't removed when deserializing B, since B contains #[serde(flatten)] and therefore deserializes as a map.

I don't think this problem is solvable without a breaking change. #[serde(flatten)] will have to be completely reworked, and possibly lots of Deserializer too if we need to accomodate it.

epage added a commit to epage/typos that referenced this issue Jan 24, 2022
Looks like `deny_unknown_fields` doesn't work with `flatten`.  See serde-rs/serde#1547.

Fixes crate-ci#406
epage added a commit to epage/typos that referenced this issue Jan 24, 2022
Looks like `deny_unknown_fields` doesn't work with `flatten`.  See serde-rs/serde#1547.

Fixes crate-ci#406
ysndr added a commit to flox/runix that referenced this issue Mar 27, 2023
This PR implements nix supported flakerefs.
Unlike the previous implementation all flakerefs are individual (generic) types.
This allows implementing converting to and from string representations on a per-type basis,
rather than necessarily covering all possible references in a single match statement.
As a side effect, implementing flake references one by one,
allowed us to match nix' behavior more closely (#3, #16).

Some flakerefs allow multiple protocols, but are essentially equivalent.
This includes `file`, `tarball`, `git` and "git forges" (e.g. `github`).
Such flakerefs have been implemented as generic types in order to share parsing logic,
and at the same time retain the ability to enforce individual origins statically.
For instance, it is now possible to define a composite type that requires local files (i.e. `git+file`, `path` `[file+]file://` or `[tarball+]file://`).

All tests that existed with the old flake_ref work with the new ones and I added a couple of "roundtrips" to assure, we do not lose information on the way.

I stumbled over a very annoying [`serde` bug](serde-rs/serde#1547 (comment)) that basically says, `deny_unknown_fields` and `flatten` cannot be used together.

That and the fact that `url` queries are not self describing structures (which [triggers](nox/serde_urlencoded#33) another [`serde` bug](serde-rs/serde#1183) related to flattening), lets me wonder if we should use serde at all for query parsing or do the parsing manually, at the cost of legibility (https://github.com/flox/runix/pull/12/files#diff-fa82b2796286fd4622869172f2187af6691578ffbdf972e853826db2d4277fbcR200-R226).
---------

Co-authored-by: Matthew Kenigsberg <[email protected]>
@Mingun
Copy link
Contributor

Mingun commented Aug 11, 2023

This is indeed a duplicate of #1358

xJonathanLEI added a commit to xJonathanLEI/starknet-jsonrpc-codegen that referenced this issue Apr 2, 2024
Types with nested flatten fields don't work with `deny_unknown_fields`
due to an upstream `serde` bug:

serde-rs/serde#1547
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

3 participants