Skip to content

untagged literal alias (best practice?) #1284

@softmarshmallow

Description

@softmarshmallow

Serde Untagged Enum Limitation: Cannot Deserialize Strings into Unit Variants

Problem

When using untagged enums with serde, it's impossible to deserialize string values into unit variants without custom deserializers. This is a common issue when parsing CSS-like values or other structured data where you want to minimize data size.

Example

#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum OpticalSizing {
    #[serde(rename = "auto")]
    Auto,
    #[serde(rename = "none")]
    None,
    Fixed(f32),
}

Expected behavior:

  • "auto"OpticalSizing::Auto
  • "none"OpticalSizing::None
  • 14.0OpticalSizing::Fixed(14.0)

Actual behavior:

Error("data did not match any variant of untagged enum OpticalSizing")

Why This Happens

With untagged enums, serde tries to deserialize the JSON value against each variant in order:

  1. Tries to deserialize "auto" as Auto (unit variant) - fails because unit variants don't accept strings
  2. Tries to deserialize "auto" as None (unit variant) - fails
  3. Tries to deserialize "auto" as f32 - fails because "auto" is not a valid f32

Use Case

This is common when parsing CSS values or similar structured data where you want to minimize data size. For example:

{
  "fontOpticalSizing": "auto", // String variant
  "fontSize": 14.0, // Numeric variant
  "lineHeight": "normal" // String variant
}

Workarounds Tested

❌ Newtype Wrapper (Doesn't Work)

#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum OpticalSizing {
    Auto(#[serde(rename = "auto")] ()),
    None(#[serde(rename = "none")] ()),
    Fixed(f32),
}

❌ Reorder Variants (Breaks Round-trip)

#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum OpticalSizing {
    Fixed(f32),  // Put numeric first
    Auto,        // Then unit variants
    None,
}

✅ Custom Deserializer (Only Working Solution)

#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
pub enum OpticalSizing {
    Auto,
    None,
    Fixed(f32),
}

impl<'de> Deserialize<'de> for OpticalSizing {
    // Custom implementation required
}

Request

Consider enhancing serde to support string deserialization into unit variants for untagged enums, especially when rename attributes are present. This would eliminate the need for custom deserializers in common cases like CSS value parsing.

Related

  • This affects any use case where you want to parse mixed string/numeric values into an enum
  • Common in web standards, CSS parsing, configuration files
  • Forces developers to write custom deserializers for what should be a simple case

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions