serde-yaml icon indicating copy to clipboard operation
serde-yaml copied to clipboard

Hint to deserialize number as a string instead, inside untagged enum

Open pwoolcoc opened this issue 4 years ago • 8 comments

I have a data structure like this:

struct Foo {
    // ...
    foos: BTreeMap<String, usize>,
    // ...
}

And sometimes, the keys for that map can be numbers, but I'd like them deserialized as strings like the other keys.

For example:

foo:
    foos:
        bar: 42
        baz: 123
        200: 321

I'd like to end up with

BTreeMap(
    "bar" => 42,
    "baz" => 123,
    "200" => 321,
)

but instead I, understandably, get a deserialization erorr since 200 is a number.

Is there any way to hint to serde_yaml that the 200 should be deserialized as a string, or do I need to make a new type that performs these conversions in it's Deserialize impl?

pwoolcoc avatar Jun 11 '20 18:06 pwoolcoc

I can't reproduce this. Next time please provide a minimal compilable code example when opening an issue.

use serde::Deserialize;
use std::collections::BTreeMap;

#[derive(Deserialize, Debug)]
struct Foo {
    foos: BTreeMap<String, usize>,
}

const Y: &str = "
foos:
    bar: 42
    baz: 123
    200: 321";

fn main() {
    println!("{:#?}", serde_yaml::from_str::<Foo>(Y).unwrap());
}
Foo {
    foos: {
        "200": 321,
        "bar": 42,
        "baz": 123,
    },
}

dtolnay avatar Jun 11 '20 18:06 dtolnay

@dtolnay sorry for the long wait, it took me a bit of debugging to get my complex example down to a minimal reproducible case. It looks like this problem only happens when an untagged enum is involved. Here's the code (playgound link):

use std::collections::BTreeMap;
use serde_yaml;
use serde::Deserialize;

fn main() {
    const Y: &str = "
foos:
    bar: 42
    baz: 123
    200: 321";
    
    const Z: &str = "
foos:
    bar: 42
    baz: 123
    '200': 321";

    // This successfully deserializes
    let foo: Foo = serde_yaml::from_str(&Y).expect("Couldn't deserialize");

    // However, when wrapped in an untagged enum, it does not:
    // 
    // swap these two lines to see that the "Z" document successfully deserializes,
    // while the "Y" document does not
    let foo: Wrapper = serde_yaml::from_str(&Y).expect("Couldn't deserialize");
    //let foo: Wrapper = serde_yaml::from_str(&Z).expect("Couldn't deserialize");
}

#[derive(Debug, Deserialize, PartialEq)]
#[serde(untagged)]
enum Wrapper {
  Foo(Foo),
}

#[derive(Debug, Deserialize, PartialEq)]
struct Foo {
  foos: BTreeMap<String, usize>,
}

pwoolcoc avatar Jun 15 '20 18:06 pwoolcoc

@dtolnay any chance you could take a look at this?

pwoolcoc avatar Jun 26 '20 13:06 pwoolcoc

I have not gotten a chance, but I would accept a PR if there is a fix in serde_yaml.

dtolnay avatar Jul 05 '20 11:07 dtolnay

i have a similar problem whereby I am storing a semantic version or version range in a yaml field. Examples:

version: 1
version: 1.2
verison: 1.2.33

I ended up creating an enum with something like:

Version {
   Major(u32),
   Minor(f32),
   Patch(String)
}

so that it would deserialize. I then implemented various convenience traits to make it simpler to work with. However, I would love to be able to decorate the field with a type hint that makes it deserialize to a String.

jlgerber avatar Aug 02 '20 22:08 jlgerber

i have a similar problem whereby I am storing a semantic version or version range in a yaml field. Examples:

version: 1
version: 1.2
verison: 1.2.33

Change your example to:

version: 1
version: 1.20
verison: 1.20.33

and you'll see the problem we're facing: you'll end up with Version::Minor(1.2) but we want to parse as a string to preserve "1.20".

jrray avatar Aug 13 '21 01:08 jrray

Here is a minimal example of a similar issue, but instead caused by the use of #[serde(flatten)] on structs: https://gist.github.com/mxndtaylor/488db8ca053d8d69e72fa0fc4f5d0a23 (playground)

Is it possible this has the same root cause? The functionality of flatten and untagged seem sort of similar from a outside perspective.

mxndtaylor avatar Aug 30 '22 17:08 mxndtaylor

Another option to resolving this is perhaps to implement a RawValue API for serde_yaml similar to that of serde_json which would surface the raw string for parsing. Some yaml users (looking at you yarn) do not know how to properly quote their strings and RawValue while more verbose would give a general-purpose escape hatch to deal with this.

arlyon avatar Oct 26 '23 10:10 arlyon