dtolnay / serde-yaml

Strongly typed YAML library for Rust
Apache License 2.0
964 stars 164 forks source link

How to opt out of !Tag syntax for enums in 0.9.0+? #298

Closed willfindlay closed 2 years ago

willfindlay commented 2 years ago

Hi, I was wondering if there is a way (besides just not upgrading the crate version) to opt out of the !Tag syntax for enums and keep the old behaviour?

nightkr commented 2 years ago

I'm wondering the same. Tags don't roundtrip cleanly to JSON, aren't compatible with the old format, and don't compose. They are also problematic for Kubernetes, which expects to be able to convert freely between JSON, YAML, and Protobuf.

For example, the following example runs fine with serde_yaml 0.8 but panics for 0.9:

use serde::Serialize;

#[derive(Serialize)]
enum Parent {
    ParentVariant(Child),
}
#[derive(Serialize)]
enum Child {
    ChildVariant(u8),
}

fn main() {
    let value = Parent::ParentVariant(Child::ChildVariant(0));
    println!("serde_yaml 0.8:");
    println!("{}", serde_yaml08::to_string(&value).unwrap());
    println!("serde_yaml 0.9:");
    println!("{}", serde_yaml09::to_string(&value).unwrap());
}
Cargo.toml ```toml [package] name = "serde-yaml-enum-composition" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] serde = { version = "1.0.141", features = ["derive"] } serde_yaml08 = { version = "0.8", package = "serde_yaml" } serde_yaml09 = { version = "0.9", package = "serde_yaml" } ```
Output ``` serde_yaml 0.8: --- ParentVariant: ChildVariant: 0 serde_yaml 0.9: thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Error("serializing nested enums in YAML is not supported yet")', src/main.rs:17:52 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace ```
dtolnay commented 2 years ago

You would need to use https://docs.rs/serde_yaml/0.9/serde_yaml/with/singleton_map/index.html on whichever field you want the opposite representation for.

Eric-Arellano commented 2 years ago

Thanks for the workaround, @dtolnay! That worked for me :)

willfindlay commented 2 years ago

@dtolnay Follow-up question: What would be the easiest/best way to make this work with a Vec<T> for example where T is an untagged enum?

Also, wouldn't it have been better to make this new behaviour opt-in? To me, it feels like wanting to use !Tag would be the exception rather than the rule considering so many use cases require compatibility with JSON.

kaikalii commented 2 years ago

@dtolnay Follow-up question: What would be the easiest/best way to make this work with a Vec<T> for example where T is an untagged enum?

Also, wouldn't it have been better to make this new behaviour opt-in? To me, it feels like wanting to use !Tag would be the exception rather than the rule considering so many use cases require compatibility with JSON.

I second this. Could this be safely reverted?

Perhaps there should be some kind of settings struct that lets you choose how you want it to work?

willfindlay commented 2 years ago

Perhaps there should be some kind of settings struct that lets you choose how you want it to work?

Or maybe even just a feature flag 👀

divergentdave commented 2 years ago

A feature flag would be unwise. In a diamond dependency scenario, crates would change each other's format by setting that feature flag. serde_yaml would get compiled once, with the flag set, to be used by both its dependents.

willfindlay commented 2 years ago

Ah good point @divergentdave, makes sense.

LGUG2Z commented 2 years ago

You would need to use https://docs.rs/serde_yaml/0.9/serde_yaml/with/singleton_map/index.html on whichever field you want the opposite representation for.

This doesn't appear to work if you apply it to a struct with an Option<Enum>; the with attribute somehow wants to treat this as a non-optional and fails the parsing when the Option<Enum> value is not included in the yaml file. Perhaps I'm missing something?

sj4nes commented 2 years ago

This is stumbling me today, I want to generate some YAML for Github Actions "Workflow", the on: field. Example:

on:
  label:
    types: [opened]

with

#[derive(Serialize, Deserialize)]
pub enum LabelTypes {
    #[serde(rename = "opened")]
    Opened,
    #[serde(rename = "labeled")]
    Labeled,
}

#[derive(Serialize, Deserialize)]
#[allow(non_camel_case_types)]
pub enum GHAEvent {
    #[serde(rename = "push")]
    Push,
    #[serde(with = "serde_yaml::with::singleton_map")]
    label { types: Vec<LabelTypes> },
}

With some extra weirdness to get the label not be uppercased "Label" but I'm still getting

on:
- !label
  - opened

adding rename = "label" to the serde annotation and leaving out the #[allow(non_camel_case_types)] still leaves me with a tagged !label list. I'm ignoring the inline vs. lined rendering since they mean the same thing.

Round tripping from YAML->Serde would be nice, but I'm not expecting it, I'm more focused on typed checked data structures doing the work and serde_yaml writing the YAML for me.

dimonomid commented 1 year ago

I know this issue is closed already, but for anyone coming across and looking for the answer on this:

What would be the easiest/best way to make this work with a Vec<T> for example where T is an untagged enum?

Apparently the solution is to use #[serde(with = "serde_yaml::with::singleton_map_recursive")] (note the _recursive part). With this, all the nested types will use the singleton map approach.

And re: this:

Also, wouldn't it have been better to make this new behaviour opt-in? To me, it feels like wanting to use !Tag would be the exception rather than the rule considering so many use cases require compatibility with JSON.

Definitely agree with that, because:

So bottom line, we replace a good working solution with a half-broken one, and make it a default, without even clear docs on how to get the old behavior (Imo the release notes should at the very least clarify it in detail, with all those attributes; it took me a while to find out the right attribute). Overall, this seems like a bad call to me. Alas.