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

Differing behaviour of serializer.serialize_bool() when using #[serde(serialize_with = "...")] #2277

Closed
mfreeborn opened this issue Sep 14, 2022 · 1 comment
Labels

Comments

@mfreeborn
Copy link

mfreeborn commented Sep 14, 2022

I have an enum which I am serializing using serde and serde_json so that some of the variants are of String type, and others are bool:

enum Filter {
    Text,
    True,
}

I can manually implement Serialize like this, which works for my use case:

impl Serialize for Filter {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        match *self {
            Self::Text => serializer.serialize_str("text"),
            Self::True => serializer.serialize_bool(true),
        }
    }
}

However, I have lots of these sorts of enums and I don't want to pay the cost of manually serializing all of the String cases every time I want one or two variants to be bool. So, I can up with this:

#[derive(Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Filter {
    Text,
    #[serde(serialize_with = "serialize_true")]
    True,
}

fn serialize_true<S>(serializer: S) -> Result<S::Ok, S::Error>
where
    S: Serializer,
{
    serializer.serialize_bool(true)
}

The problem now is that Filter::True serializes (in json) to Object({"true": true}), whereas with the manual impl, it serializes to just a plain true.

I tried adding #[serde(untagged)] to the enum, but whilst that fixes the bool case, the String cases now get serialized to null!

Is this expected behaviour that calling serializer.serialize_bool() has a different output when called from a serialize_with context versus a manual impl context?

If this is better in the serde_json issue tracker, let me know...

@kangalio
Copy link

The problem now is that Filter::True serializes (in json) to Object({"true": true}), whereas with the manual impl, it serializes to just a plain true.

This is intentional, because the default enum representation is "externally tagged" (https://serde.rs/enum-representations.html):

  • Filter::Text => {"Text": null}
  • Filter::True => {"True": true}

Serde seems to special case when the inner data is null, and writes just the variant name, which is the reason you saw "Text" instead of {"Text": null}.

I tried adding #[serde(untagged)] to the enum, but whilst that fixes the boolcase, theStringcases now get serialized tonull`!

This is also intentional, because #[serde(untagged)] just writes the inner data of the enum variant, and that is null and true respectively.


What you want is to set the tag of Filter::True to true and leave the other tags to their default values:

#[derive(Serialize)]
#[serde(rename_all = "lowercase")]
enum Filter {
    Text,
    #[serde(rename = true)]
    True,
}

You can currently only rename enum variants to strings, not to other data types. This is a missing feature in serde-derive and should be implemented. PRs to add support for non-string enum tags already exist for adjacently- and internally-tagged enums (#2056), but not for externally tagged enums

As a workaround, you can create a macro that generates your enum and its Serialize impl

@dtolnay dtolnay closed this as completed Jul 9, 2023
@dtolnay dtolnay added the support label Jul 9, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

No branches or pull requests

3 participants