#macro-derive #enums #serde-derive

macro nested-deserialize

A very special-case implementation of serde::Deserialize for untagged nested enums

1 unstable release

Uses new Rust 2024

new 0.1.0 Feb 22, 2026

#534 in Procedural macros

MIT license

12KB
121 lines

nested-deserialize

A very special-case implementation of serde::Deserialize for untagged nested enums (also known as merged enums or enum unions).

It applies to the following case:

  • "Outer" enum is untagged
  • Outer enum has 1-length tuple variants
  • ..of which the value is an enum
    • which should not have colliding variant names with its "sibling enums" used in other variants of the outer enum (also known as the term non-overlapping)
    • which must implement serde::Deserialize
    • and strum::VariantNames
      • where the renaming rules (if any) must be in sync with serde

It effectively achieves the same as copy-pasting the variants of each inner enum into an outer enum (or using a macro to do this), and then implementing TryFrom for the inner enums.

Example

use nested_deserialize::NestedDeserialize;
use strum::VariantNames;

#[derive(Deserialize, VariantNames, Debug)] // `VariantNames` required, subject to change
enum Physics {                              // "Inner" enum
    Velocity { m_s: f64 },
    Acceleration { m_s2: f64 },
}

#[derive(Deserialize, VariantNames, Debug)]
enum Chemistry {                            // "Inner" enum
    MolarMass { g_mol: f64 },
    PhValue { ph: f64 },
}

#[derive(NestedDeserialize, Debug)]         // This crate provides `NestedDeserialize`
enum ScienceData {                          // "Outer" enum
    Phys(Physics),
    Chem(Chemistry),
}

Differences with other solutions

#[serde(untagged)]

  • #[serde(untagged)] tries to parse as each variant in order
  • nested-deserialize delegates to variants based on the tag (can only support enums)

  • #[serde(untagged)] discards any errors from parsing as the inner variants
  • nested-deserialize provides specialized errors for the following:
    • unknown variant (tag)
    • invalid tagged enum shape
    • the error from the variant, if a valid variant was specified, but could not be deserialized into

deserialize_untagged_verbose_error

  • deserialize_untagged_verbose_error tries to parse as each variant in order (like #[serde(untagged)])
  • nested-deserialize delegates to variants based on the tag (can only support enums)

  • deserialize_untagged_verbose_error returns errors from all variants it tried
    • which is good for debugging
    • ..but most likely you already knew which variant you were trying to use
  • nested-deserialize provides the error from trying to deserialize as the specified variant
    • or provides a list of valid variants if none matched
    • or provides a generic error message in case of an invalid type

serde-untagged

  • serde-untagged is not a proc macro
    • requires writing boilerplate
    • ultimate control
  • nested-deserialize is a proc macro
    • apply it to an enum and done
    • may not satisfy complicated use-cases

AI usage disclosure

I vibecoded this, it's about 150 lines of code (pretty simple), and it seems to work for the described usecase. I plan to rewrite parts if needed, by hand, once I fully understand how proc macros work.

If you're interested in this crate/need this functionality, and willing to spend some time helping me with polishing it, please reach out to me.

To-Do / Wishlist

Dependencies

~130–520KB
~12K SLoC