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

"Inlining" enum into containing struct #761

Closed
jonas-schievink opened this issue Feb 12, 2017 · 4 comments
Closed

"Inlining" enum into containing struct #761

jonas-schievink opened this issue Feb 12, 2017 · 4 comments
Labels

Comments

@jonas-schievink
Copy link

I'm trying to (de)serialize a TOML config which basically allows the user to choose a mode. It essentially looks like this:

struct Config {
    name: String,
    other_common_stuff: u64,
    mode: ConfigMode,
}

enum ConfigMode {
    Url {
        url: String,
    }
    
    Dummy {
        print: bool,
    }
}

I want the config file to look somewhat like this:

name = "bla"
other_common_stuff = 456
mode = "url"
url = "http://example.com"

Or, for selecting the Dummy mode:

name = "bla"
other_common_stuff = 456
mode = "dummy"
print = "Hello, World!"

So I basically need to "inline" the ConfigMode into the Config struct for (de)serialization. Since serde supports untagged enums, this doesn't seem too hard to implement to me, right?

@dtolnay
Copy link
Member

dtolnay commented Feb 12, 2017

We are tracking better support for this case in #119. For now there are two possible workarounds but neither one is great. You can deserialize as one of these and then immediately convert it to your better representation for the rest of your program to use.

#[derive(Serialize, Deserialize)]
#[serde(untagged)]
enum Config {
    Url {
        name: String,
        other_common_stuff: u64,
        url: String,
    }
    
    Dummy {
        name: String,
        other_common_stuff: u64,
        print: bool,
    }
}

or

#[derive(Serialize, Deserialize)]
struct Config {
    name: String,
    other_common_stuff: u64,

    #[serde(skip_serializing_if = "Option::is_none")]
    url: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    print: Option<bool>,
}

Once we implement #119 it will look like this:

#[derive(Serialize, Deserialize)]
struct Config {
    name: String,
    other_common_stuff: u64,

    #[serde(flatten)]
    mode: ConfigMode,
}

#[derive(Serialize, Deserialize)]
#[serde(untagged)]
enum ConfigMode {
    Url {
        url: String,
    }
    
    Dummy {
        print: bool,
    }
}

@jonas-schievink
Copy link
Author

Thanks, that's exactly what I'm looking for!

@dtolnay dtolnay closed this as completed Feb 13, 2017
@rofrol
Copy link

rofrol commented Nov 7, 2018

Working example https://play.rust-lang.org/?gist=6457ea3e0824e80c0f1270cacb052e57

Prints:

Config { name: "bla", other_common_stuff: 456, mode: Url { url: "http://example.com" } }
Config { name: "bla", other_common_stuff: 456, mode: Dummy { print: "Hello, World!" } }

How to nicely remove url and print and have this instead?

Config { name: "bla", other_common_stuff: 456, mode: Url { "http://example.com" } }
Config { name: "bla", other_common_stuff: 456, mode: Dummy { "Hello, World!" } }

@vallentin
Copy link

Technically, using untagged is a bit misleading for OPs case. Yes, mode is still in the config, but nothing is "chosen" based on it (as OP said). So if there's multiple variants with the same fields, e.g. a Url and ShortUrl both with a url: String field, then this solution falls flat.

Additionally, if the containing struct is tagged with deny_unknown_fields then even having mode in the config causes deserialization to fail. Which is undesired, if deny_unknown_fields is used to catch unexpected fields.

Yes, untagged is working as expected in itself. I just wanted to point out, that technically, based on the issue then it isn't a proper solution, it just works.

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

4 participants