A very special-case implementation of serde::Deserialize for untagged nested enums.
  • Rust 84%
  • Nix 16%
Find a file
2026-02-22 02:51:44 +01:00
src add this slop 2026-02-22 00:18:34 +01:00
.gitignore add this slop 2026-02-22 00:18:34 +01:00
Cargo.toml prepare for publishing 2026-02-22 02:51:44 +01:00
flake.lock add this slop 2026-02-22 00:18:34 +01:00
flake.nix add this slop 2026-02-22 00:18:34 +01:00
LICENSE.md prepare for publishing 2026-02-22 02:51:44 +01:00
README.md write a readme 2026-02-22 01:38:30 +01:00

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