dtolnay / serde-yaml

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

`singleton_map` requires optional values #310

Closed LGUG2Z closed 2 years ago

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?

Originally posted by @LGUG2Z in https://github.com/dtolnay/serde-yaml/issues/298#issuecomment-1209928426

I have included a minimal reproducible example demonstrating this case:

use serde::Deserialize;
use serde::Serialize;

#[derive(Serialize, Deserialize)]
enum MyEnum {
    VariantOne { data: usize },
    VariantTwo { data: usize },
}

#[derive(Serialize, Deserialize)]
struct MyStruct {
    required_member: usize,
    #[serde(with = "serde_yaml::with::singleton_map")]
    optional_member: Option<MyEnum>,
}

fn main() {
    serde_yaml::from_str::<MyStruct>("required_member: 0").unwrap();
}

This gives the following error:

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Error("missing field `optional_member`", line: 1, column: 1)', src\main.rs:18:60
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
error: process didn't exit successfully: `target\debug\serde-yaml-optional-singleton-repro.exe` (exit code: 101)
dtolnay commented 2 years ago

You would need to use serde(default) if you want to support the field being absent. serde(with) requires to be explicit about this because it is not necessarily what you want when replacing the deserialization logic for a type.

#[derive(Serialize, Deserialize)]
struct MyStruct {
    required_member: usize,
    #[serde(with = "serde_yaml::with::singleton_map", default)]
    optional_member: Option<MyEnum>,
}
firstdorsal commented 2 years ago

How would this work with something more complex?

This does not work

#[derive(Deserialize, Debug, Clone)]
pub struct HttpConfig {
    #[serde(with = "serde_yaml::with::singleton_map", default)]
    pub middlewares: Option<HashMap<String, Enum>>,
}

It would be awesome if this (imo weird) yaml feature could be turned on/of in the crates feature flags

thank you and all the best

firstdorsal commented 2 years ago

I just found out how it works:

#[derive(Deserialize, Debug, Clone)]
pub struct HttpConfig {
    #[serde(with = "serde_yaml::with::singleton_map_recursive")]
    pub middlewares: Option<HashMap<String, Enum>>,
}