serde-yaml
serde-yaml copied to clipboard
Hint to deserialize number as a string instead, inside untagged enum
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?
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 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>,
}
@dtolnay any chance you could take a look at this?
I have not gotten a chance, but I would accept a PR if there is a fix in serde_yaml.
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.
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"
.
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.
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.