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

Allow to define numbers for adjacently tagged enums #2479

Closed
danielo515 opened this issue Jun 20, 2023 · 9 comments
Closed

Allow to define numbers for adjacently tagged enums #2479

danielo515 opened this issue Jun 20, 2023 · 9 comments

Comments

@danielo515
Copy link

This could look similar to #745 , but it is not.
Some libraries in some other programming languages are serializing enums like this:

[
"TheName"
{ thePayload }
]

Basically a tuple. Exactly the same as an adjacently tagged enum but the key being 0 and the payload being 1.
I think implementing this in serde will be easier than having to implement it myself for every enum I have to deal with. To give you a little example, here is an implementation I came up with the great help of the rust community:

struct TupleVisitor;

impl<'de> serde::de::Visitor<'de> for TupleVisitor {
    type Value = Background;

    fn expecting(
        &self,
        formatter: &mut std::fmt::Formatter,
    ) -> std::fmt::Result {
        formatter.write_str("a tuple with two elements")
    }

    fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
    where
        A: serde::de::SeqAccess<'de>,
    {
        let variant = seq
            .next_element::<String>()?
            .ok_or_else(|| serde::de::Error::invalid_length(0, &self))?;

        let background = match &variant[..] {
            "Solid" => {
                Background::Solid(seq.next_element()?.ok_or_else(|| {
                    serde::de::Error::invalid_length(1, &self)
                })?)
            }
            "Gradient" => {
                Background::Gradient(seq.next_element()?.ok_or_else(|| {
                    serde::de::Error::invalid_length(1, &self)
                })?)
            }
            "Image" => {
                Background::Image(seq.next_element()?.ok_or_else(|| {
                    serde::de::Error::invalid_length(1, &self)
                })?)
            }
            _ => return Err(serde::de::Error::custom("unknown type")),
        };

        Ok(background)
    }
}

impl<'de> Deserialize<'de> for Background {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer.deserialize_tuple(2, TupleVisitor)
    }
}

As you can see, it is a lot of boilerplate to have to repeat it so many times (I have dozens of this)

@Mingun
Copy link
Contributor

Mingun commented Jun 20, 2023

This is already supported -- as you can see (run Tools->Expand macros), __Visitor handles visit_seq

@danielo515
Copy link
Author

Yes, all is need is to annotate with:

#[serde(tag = "0", content = "1")]

Sorry, I was making some other mistakes and I thought this was not supported.

@Mingun
Copy link
Contributor

Mingun commented Jun 20, 2023

Just a side note: you can use any names for tag and content fields. The generated implementation will perform deserialization from both

{
  "<selected value of 'tag'>": "<data tag>",
  "<selected value of 'content'>": ...,
}

and

[
  "<data tag>",
  ...
]

In your case it will recognize

{
  "0": "<data tag>",
  "1": ...
}

@danielo515
Copy link
Author

[
  "<data tag>",
  ...
]

In your case it will recognize

{
  "0": "<data tag>",
  "1": ...
}

My case is just like the first one, not than the second one. Not sure what you meant, but thanks for your answer

@Mingun
Copy link
Contributor

Mingun commented Jun 21, 2023

I mean that both inputs are valid if you specify

#[serde(tag = "0", content = "1")]

In other words, specific values ​for tag and content are not important for deserialization from the array to work. They are used only as mapping keys when you deserialize from mapping. Adjacently tagged enums are always can be deserialized either from mapping or from the 2-element array

@danielo515
Copy link
Author

Adjacently tagged enums are always can be deserialized either from mapping or from the 2-element array

Sorry, but I think I'm not understanding you correctly.
Indeed, if I specify:

#[serde(tag = "0", content = "1")]

It works for both objects and arrays. But this las sentence of yours that I quoted seems to suggest that adjacently tagged enums work out of the box without the need for this annotation. As far as I know, there is no other way to opt-in into adjacently tagged enums.

@Mingun
Copy link
Contributor

Mingun commented Jun 21, 2023

seems to suggest that adjacently tagged enums work out of the box without the need for this annotation.

Of course not, annotation is needed. I just try to pay your attention, that 0 and 1 in #[serde(tag = "0", content = "1")] not the indexes in the array as you, it seems, thought in the beginning. It will also work if you specify #[serde(tag = "1", content = "0")] or #[serde(tag = "-42", content = "100500")] or whatever else.

@danielo515
Copy link
Author

seems to suggest that adjacently tagged enums work out of the box without the need for this annotation.

Of course not, annotation is needed. I just try to pay your attention, that 0 and 1 in #[serde(tag = "0", content = "1")] not the indexes in the array as you, it seems, thought in the beginning. It will also work if you specify #[serde(tag = "1", content = "0")] or #[serde(tag = "-42", content = "100500")] or whatever else.

Oh, I think I understand what you mean @Mingun , it is because of this #2480 right?

@Mingun
Copy link
Contributor

Mingun commented Jun 22, 2023

The same approach is used to deserialize a pseudo-struct with "tag" and "content" fields, yes. You can think about that as if deserialization of that type is performed:

#[derive(Deserialize)]
struct AdjacentlyEnumContainer<T> {
  #[serde(rename = "<your tag>")]
  tag: String,
  #[serde(rename = "<your content>")]
  content: T,
}

except that the visitor is implemented manually and the type T actually never exist. As I mentioned early in this thread, you can see the __Visitor::visit_seq that makes things possible.

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