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

Struct with tag and deny_unknown_fields cannot deserialize #2666

Open
schneems opened this issue Dec 20, 2023 · 3 comments
Open

Struct with tag and deny_unknown_fields cannot deserialize #2666

schneems opened this issue Dec 20, 2023 · 3 comments

Comments

@schneems
Copy link

schneems commented Dec 20, 2023

Hello, thanks for serde!

Context

I want to prevent accidentally deserializing data from one struct into another when they share fields, as the semantics might be completely different. This is a part of my work here heroku/buildpacks-ruby#246 (comment) where I'm trying to handle different versions of a toml file stored on disk that would map to different structs.

Expected

I would expect that when I use #[serde(tag = "struct_tag", deny_unknown_fields)] that I can serialize a struct to a string, then deserialize that same string back to the original struct.

Actual

This code results in an error

use serde::{Deserialize, Serialize};

#[derive(Deserialize, Serialize, Debug, Clone, Eq, PartialEq)]
#[serde(tag = "struct_tag")]
#[serde(deny_unknown_fields)]
struct ROFLtagV1 {
    name: String,
}

fn main() {
    let metadata = ROFLtagV1 {
        name: String::from("richard"),
    };

    let toml_string = toml::to_string(&metadata).unwrap();
    assert_eq!(
        "struct_tag = \"ROFLtagV1\"\nname = \"richard\"".trim(),
        toml_string.trim()
    );
   
    toml::from_str::<ROFLtagV1>(&toml_string).unwrap();
}

Playground link: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=d8e02a6d877bc57d24d60dcf93a1df11

Result:

called `Result::unwrap()` on an `Err` value: Error { inner: Error { inner: TomlError { message: "unknown field `struct_tag`, expected `name`", original: Some("struct_tag = \"ROFLtagV1\"\nname = \"richard\"\n"), keys: [], span: Some(0..10) } } }

It serializes as I would expect, but then it gives an error saying that it doesn't know about the struct_tag field.

Addendum

I understand that the tag feature is originally for enums so this might be an unexpected use case. If there's a better way to tell serde that it should preserve the struct name (or some other unique key/value combination) in order to be strict about deserializing then please let me know.

Update:

@CJKay
Copy link

CJKay commented Jan 27, 2025

I'm currently working around this using IgnoredAny, e.g.:

#[derive(Deserialize, Serialize, Debug, Clone, Eq, PartialEq)]
#[serde(tag = "struct_tag")]
#[serde(deny_unknown_fields)]
struct ROFLtagV1 {
    name: String,

    #[serde(default, skip_serializing)]
    struct_tag: serde::de::IgnoredAny,
}

It'd be great to have this behaving as expected.

@schneems
Copy link
Author

Thats an interesting idea. Can you link me to a playground link or post a raw rust example you're using? That snippet cannot compile as serde::de::IgnoredAny is not Eq:

$ cargo run
   Compiling lol v0.1.0 (/private/tmp/df2785de1c0822a767b72fe11814d9bd/lol)
error[E0277]: the trait bound `IgnoredAny: Eq` is not satisfied
   --> src/main.rs:10:5
    |
3   | #[derive(Deserialize, Serialize, Debug, Clone, Eq, PartialEq)]
    |                                                -- in this derive macro expansion
...
10  |     struct_tag: serde::de::IgnoredAny,
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Eq` is not implemented for `IgnoredAny`
    |
note: required by a bound in `AssertParamIsEq`
   --> /Users/rschneeman/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/cmp.rs:363:31

I'm unfamiliar with using that type, I tried making a newtype wrapper to see if I could iml a custom Eq on my newtype but abandoned it pretty quickly.

@CJKay
Copy link

CJKay commented Jan 28, 2025

Ah, I didn't see the Eq derive there - that probably throws a spanner in the works, then.

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

2 participants