Struct with `tag` and `deny_unknown_fields` cannot deserialize
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 https://github.com/heroku/buildpacks-ruby/pull/246#discussion_r1432008482 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:
- Playground example showing the problem I'm dealing with: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=e26033d3c8c3c34414fe594674f6d053
- Asking on Stack Overflow https://stackoverflow.com/questions/77700360/prevent-a-serialized-struct-in-rust-from-being-deserialized-into-a-different-one
- Asking on Mastodon https://ruby.social/@Schneems/111619648760386358
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.
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.
Ah, I didn't see the Eq derive there - that probably throws a spanner in the works, then.
I stumbled upon the same issue as I wanted to make the tag field required. It seems to be ignored when deserializing (!) Or can somebody tell me how to do that?