Mark LeMoine
Mark LeMoine

Reputation: 4628

Is there a way to "flatten" enums for (de)serialization in Rust?

I have an enum that is composed of other enums, similar to the following (serde derives and annotations omitted for brevity):

enum Main {
    A(SubA),
    B(SubB),
}

enum SubA { X1, X2, X3 }
enum SubB { Y1, Y2, Y3 }

I'd love to be able to use serde to deserialize a string such as "X1" or "Y3", and automatically get back a Main::A(SubA::X1) or Main::B(SubB::Y3), respectively.

I understand that serde supports a #[serde(transparent)] attribute on newtype-style structs, which would do what I like. However, it only seems to work on the struct or enum level, not at the enum-variant level. Is there a way I can automagically get the behavior I'm looking for? I can ensure that there would be no overlap in any of the variant names in any of the contained sub-enums.

Aside from wanting to remain on Rust 2018 stable, I have no other restrictions on my project, and I'm open to any crate suggestions that would help.

Upvotes: 12

Views: 8508

Answers (1)

michalsrb
michalsrb

Reputation: 4891

It works pretty much out of the box if you put serde(untagged) on the outer enum and nothing special on the inner enums:

use serde_derive::{Deserialize, Serialize};

#[derive(Debug, Deserialize, Serialize, PartialEq)]
#[serde(untagged)]
enum Main {
    A(SubA),
    B(SubB),
}

#[derive(Debug, Deserialize, Serialize, PartialEq)]
enum SubA { X1, X2, X3 }

#[derive(Debug, Deserialize, Serialize, PartialEq)]
enum SubB { Y1, Y2, Y3 }

fn main() {
    let x1 = Main::A(SubA::X1);
    let y2 = Main::B(SubB::Y2);

    assert_eq!(serde_json::to_string(&x1).unwrap(), "\"X1\"");
    assert_eq!(serde_json::to_string(&y2).unwrap(), "\"Y2\"");

    assert_eq!(serde_json::de::from_str::<Main>("\"X1\"").unwrap(), x1);
    assert_eq!(serde_json::de::from_str::<Main>("\"Y2\"").unwrap(), y2);
}

Upvotes: 19

Related Questions