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

#[serde(other)] with forwarding raw string #1701

Open
WaffleLapkin opened this issue Dec 28, 2019 · 10 comments
Open

#[serde(other)] with forwarding raw string #1701

WaffleLapkin opened this issue Dec 28, 2019 · 10 comments

Comments

@WaffleLapkin
Copy link

I'm trying to deserialize error from rest service to enum.

For now, I have code like that:

#[derive(..., Deserialize, Serialize)]
enum Error {
    #[serde(rename = "Error: A")]
    A,
    #[serde(rename = "B is bad")]
    B,
    /* Imagine there are *A LOT* of errors */
}

And it works, but if service will add a new error, my code won't deserialize it. What I want to write is smt like this:

enum Error {
    /* ... */
    #[serde(other)]
    Unknown(String),
}

And if there will be a new error, it will be deserialized to Unknown("Message of a new error"). But unfortunately #[serde(other)] works only for unit variants :(

Is it possible to do smt like this without writing a custom (de)serializer? (writing a custom one will be painful due to amount of errors)

(example of what I want on playground)

@Diggsey
Copy link

Diggsey commented Feb 11, 2020

I would also really like to see this feature.

@WaffleLapkin there is a workaround:

#[derive(..., Deserialize, Serialize)]
#[serde(untagged)]
enum NonExhaustive<T> {
    Known(T),
    Unknown(String),
}

#[derive(..., Deserialize, Serialize)]
enum KnownError {
    #[serde(rename = "Error: A")]
    A,
    #[serde(rename = "B is bad")]
    B,
}

type Error = NonExhaustive<KnownError>;

@WaffleLapkin
Copy link
Author

Nice workaround, thanks! Still think that this needs a more ergonomic way, but it's better than nothing :)

@Heliozoa
Copy link

Heliozoa commented Aug 2, 2020

I could also use something like this. My use case is "vague" enumerations, where a field can clearly only have values within some set, but it's difficult to definitively figure out what the set is. I'm working with a JSON API with a few fields like this, and the values originally come from various places in a C++ codebase that I'm not too confident in navigating.

A workaround that doesn't expose a wrapper type to the user and doesn't involve writing manual Deserialize implementations for every type would also be fine. I tried coming up with one but I wasn't able to, I'm not particularly proficient in serde though.

@WaffleLapkin
Copy link
Author

@Heliozoa if you don't want to expose the wrapper, you can make it private and then use #[serde(from = "FromType")], e.g.:

#[derive(..., Deserialize, Serialize)]
#[serde(untagged)]
enum NonExhaustive<T> {
    Known(T),
    Unknown(String),
}

#[derive(..., Deserialize, Serialize)]
enum KnownError {
    #[serde(rename = "Error: A")]
    A,
    #[serde(rename = "B is bad")]
    B,
}

#[derive(..., Deserialize, Serialize)]
#[serde(from = NonExhaustive<KnownError>]
pub enum Error { A, B, Unknown(String) }

impl From<NonExhaustive<KnownError>> for Error { ... }

(but I didn't check this, so not sure if it relly works)

@ryzhyk
Copy link

ryzhyk commented Aug 12, 2020

@Diggsey , I use a similar solution based on untagged in my project, but it has an important downside that it obfuscates deserialization errors: if the input data is in the wrong format, e.g., it has a known tag but one of the fields associated with this tag is missing, you end up with unhelpful "data did not match any variant" error.

I therefore support @WaffleLapkin 's feature request: it would be great if serde(other) allowed non-unit variants.

@Mingun
Copy link
Contributor

Mingun commented Oct 21, 2020

@WaffleLapkin, if I correctly understand your needs, this is already possible with this code:

#[serde(field_identifier)]
enum Error {
    /* ... */
    Unknown(String),// note, that #[serde(other)] is not required
}

Newtype variant automatically considered, as having #[serde(other)]. But of course, I find it a little frustrated that having #[serde (other)] leads to an error, which is not obvious at all. @dtolnay , maybe allow use of #[serde(other)] even in such cases, ie:

#[serde(field_identifier)]
enum Error {
    /* ... */
    #[serde(other)]
    Unknown(String),
}

@WaffleLapkin
Copy link
Author

@Mingun hmm, feels like that's it.

Though, I can't find any mentions of field_identifier attribute in the docs, is it even documented?

@WaffleLapkin
Copy link
Author

Ah, the fact that this is undocumented is already tracked in #1238 so I'm going to close this issue.

@Emilgardis
Copy link

While field_identifier works great for deserializing, it does not work for serialization

@roblabla
Copy link
Contributor

roblabla commented Aug 29, 2021

This issue should be reopened. field_identifier does not support serialization, which makes it an insufficient solution to this problem.

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

7 participants