-
-
Notifications
You must be signed in to change notification settings - Fork 793
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
Tagged enums should support #[serde(other)] #912
Comments
Could you give some example of how you would expect this to work? Please show the enum definition, the input data, and the output value you expect to get from deserializing. |
Say I have an enum
When deserializing, As the deserialization input could be indeed anything, it would be nice to have a default (or fallback) choice. |
Cool, I am on board with this. I would love a PR to implement it! |
This would be really great for reaching feature-parity with protocol buffers. In proto3 Java, unknown variants deserialize to a special Example enum TrafficClass {
#[serde(other)]
Unknown,
Undesired,
BestEffort,
Sensitive,
} In the future I might want to add Open Questions I would assume the most sensible thing would be to only allow this annotation on data-less variants; can you think of a way it would work otherwise? I don't think it'd be wise to try to deserialize the unknown tag's data into some other variant's data. |
I don't know if it makes sense for all deserializers, but for JSON at least, it would be nice to be able to capture either the raw JSON (as a string), or better yet, the |
My use case is something like this:
Where, when parsing JSON, the unrecognised variant would be parsed as Also, now that I think about it, a stretch goal would be as parsing with many unrecognised variants, deciding between them using similar, structure and type-based logic as |
Any news on that? This would be extremely useful. I have needed that for such a long time already. |
We are using this workaround until it's supported #[derive(Serialize, Deserialize, Debug)]
pub enum FieldKind {
date,
email,
menu,
#[serde(skip_deserializing)]
unknown,
}
fn deserialize_field_kind<'de, D>(deserializer: D) -> Result<FieldKind, D::Error>
where
D: Deserializer<'de>,
{
Ok(FieldKind::deserialize(deserializer).unwrap_or(FieldKind::Unknown))
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Field {
#[serde(deserialize_with = "deserialize_field_kind")]
kind: FieldKind,
} hope that helps |
@dtolnay: I might have some bandwidth to implement this. After a quick look through the serde source, some questions:
Are you onboard with that name? |
|
I am fighting this too. I have: #[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
#[serde(tag = "kind", rename_all="lowercase" )]
pub enum DocumentChangeOperation {
Create(CreateFile),
Rename(RenameFile),
Delete(DeleteFile),
#[serde(other)]
Edit(TextDocumentEdit),
} Where the Edit variant doesn't actually have a |
I am also interested in this feature. I would like this to choose a common default variant when no I would want the following: [[binds]]
address = "0.0.0.0:80"
[[binds]]
type = "tls"
address = "0.0.0.0:443"
cert = "/some/file/path" #[derive(Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum Bind {
#[serde(other)]
Http {
address: SocketAddr,
},
#[serde(alias = "tls")]
HttpsTls {
address: SocketAddr,
cert: PathBuf,
}
} but with Is there any progress on this issue? |
Hmm, maybe |
any suggestion how I would do workaround for a I tried: fn deserialize_vec_field_kind<'de, D>(deserializer: D) -> Result<Vec<FieldKind>, D::Error>
where
D: Deserializer<'de>,
{
struct VecFieldKind(PhantomData<fn() -> Vec<FieldKind>>);
impl<'de> Visitor<'de> for VecFieldKind {
type Value = Vec<FieldKind>;
fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
formatter.write_str("Array of field kind.")
}
fn visit_seq<S>(self, mut seq: S) -> Result<Vec<FieldKind>, S::Error>
where
S: SeqAccess<'de>,
{
let mut field_kinds: Vec<FieldKind> = Vec::new();
while let element = seq.next_element().unwrap_or(Some(FieldKind::Unknown)) {
field_kinds.push(element.unwrap())
}
Ok(field_kinds)
}
}
deserializer.deserialize_seq(VecFieldKind(PhantomData))
} but I end up in an infinite loop. |
while let element = seq.next_element().unwrap_or(Some(FieldKind::Unknown)) {
field_kinds.push(element.unwrap())
} You should match on the error state, not just the loop {
match seq.next_element() {
Ok(Some(element)) => field_kinds.push(element),
Ok(None) => break, // end of sequence
Err(_) => field_kinds.push(FieldKind::Unknown),
}
} |
My bad didn't read that documentation correctly, but even if I add a break on |
Hmm. Well, something is making |
Instead of talking about workarounds: what would be a viable path forward to get this feature into serde itself? |
I don't think there is one. If you have a look at unmerged PRs and activitiy on those and on recently-closed ones you will see that that serde is effectively feature-frozen. |
Another featureful workaround: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=aabb4e581775d2f3b82d4725ab85448f |
In my case, I'm hitting this same issue with an |
I'm hitting up against this and have tried: #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum ValueEntry {
A(Vec<String>),
B(Vec<String>),
// Provide fallback in case we're deserialising from a newer release
// of a subcommand that include json that we don't recognise
//
// 'other' means that we use this variant if none of the others match.
// 'ignore_contents' is used as 'other' only works if the entry deserializes to
// a unit value so we override the deserialization to ignore everything and
// return a unit value.
#[serde(other, deserialize_with = "ignore_contents")]
Unknown,
}
fn ignore_contents<'de, D>(deserializer: D) -> Result<(), D::Error>
where
D: Deserializer<'de>,
{
// Ignore any content at this part of the json structure
deserializer.deserialize_ignored_any(serde::de::IgnoredAny);
// Return unit as our 'Unknown' variant has no args
Ok(())
} I am new-ish to rust and serde so I've no reason to have confidence in this but it seems promising from some early tests so I thought I'd record it before I forget. I only have one kind of entry to test it on at the moment so it might fail for other variations.
|
In my case, I'm getting this problem deserializing to |
I just ran into this problem and can confirm that the workaround provided by @michaeljones worked in my case. Many thanks! |
Also running into this exact thing. My configuration format deals with program names and configurations, and certain programs should be detected and deserialized especially, or fallback to a standard representation, like so: #[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
enum Program {
Bash(BashConfig),
Vim(VimConfig),
// ...
// What goes here?
Other(String, GenericConfig)
} Not sure what the most appropriate workaround is, if any |
@LunarLambda You can create a newtype ProgramWrapper and manually implement Deserialize for it, with fallback to Other. |
Not really sure how to do that easiest. Since I'm using an externally tagged enum, the serialized form I essentially need is a e.g. in json: {
"vim": { -- VimConfig contents -- },
"other": { -- GenericConfig contents -- }
} ideally, the datatype I would like to work with would be something more like |
Just wanted to mention the GitHub: https://github.com/bk-rs/serde-ext example: use serde_enum_str::{Deserialize_enum_str, Serialize_enum_str};
#[derive(Deserialize_enum_str, Serialize_enum_str, Debug, PartialEq, Eq)]
enum Foo {
A,
B,
#[serde(other)]
Other(String),
} |
Since I ran into this problem I might as well also throw a solution here.
Rust Playground example: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=ad002072c10d3055f57c467972ac7aca
If you don't care about any of the fields, you could just use an empty struct:
Or a struct that contains certain fields if you expect all of your 'catch-all' cases to still fit some kind of pattern (for example if they are internally tagged and you want to print out the name of the tag that wasn't recognized, that can just be a field in the struct Compared to the creative solution with
|
For posterity, this is implemented in serde. The attribute to use is The docs do mention that variants can take the attribute. However, it seems like people are still getting stuck on this problem (myself included), so maybe it'd helpful to update the documentation. use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub enum OtherString {
Foo,
#[serde(untagged)]
Other(String)
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub enum OtherValue {
Bar,
#[serde(untagged)]
Other(serde_json::Value)
}
fn main() {
let s: OtherString = serde_json::from_str(r#""Foo""#).unwrap();
assert_eq!(s, OtherString::Foo);
let s: OtherString = serde_json::from_str(r#""not an enum value""#).unwrap();
assert_eq!(s, OtherString::Other("not an enum value".to_string()));
let s: OtherValue = serde_json::from_str(r#""Bar""#).unwrap();
assert_eq!(s, OtherValue::Bar);
let s: OtherValue = serde_json::from_str(r#"{"a": 5}"#).unwrap();
assert_eq!(s, OtherValue::Other(serde_json::json!({"a": 5})));
} I did some quick sleuthing, and it was implementing 6 months ago in 48e5753 , so in the scheme of things it's a quite recent addition. |
I think tagged enums should also allow
#[serde(other)]
to mark a enum field as default. This will make the tagged enums much more useful.Or is there already a way to do this that I don know?
The text was updated successfully, but these errors were encountered: