jonah
October 3, 2019, 2:39pm
1
Hello!
I have a TOML config file which holds pairs of u32
s and bool
s. (In reality, it's gpio numbers to turn on and off, but that's not important.) I would like to have serde handle the deserialization for me, but that didn't work out of the box. What's the best way to handle this?
Many thanks!
use serde::Deserialize;
use std::collections::HashMap;
use toml;
#[derive(Deserialize, Debug)]
struct Config {
data: HashMap<u32, bool>,
}
fn main() {
let toml_str = r#"
data.10 = true
data.12 = false
"#;
let _config: Config = toml::from_str(toml_str).unwrap();
}
(Playground )
Errors:
Compiling playground v0.0.1 (/playground)
Finished dev [unoptimized + debuginfo] target(s) in 2.07s
Running `target/debug/playground`
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Error { inner: ErrorInner { kind: Custom, line: Some(1), col: 10, at: Some(11), message: "invalid type: string \"10\", expected u32", key: ["data"] } }', src/libcore/result.rs:1084:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
osrust
October 3, 2019, 4:35pm
2
May be you could use .parse
if it helps your use case.
use serde::Deserialize;
use std::collections::HashMap;
use toml::Value;
#[derive(Deserialize, Debug)]
struct Config {
data: HashMap<u32, bool>,
}
fn main() {
let toml_str = r#"
data.10 = true
data.12 = false
"#.parse::<Value>().unwrap();
println!("{:?}", toml_str);
}
Gives
Table({"data": Table({"10": Boolean(true), "12": Boolean(false)})})
You can then use the square bracket notation to access (and cast, if needed) values and setup the struct.
toml_str["data"]["10"]
See docs here .
ExpHP
October 3, 2019, 5:07pm
3
Keys in TOML are always strings. You can use #[serde(deserialize_with)]
to convert them after the fact:
#[derive(Deserialize, Debug)]
struct Config {
#[serde(deserialize_with = "deserialize_data")]
data: HashMap<u32, bool>,
}
fn deserialize_data<'de, D>(deserializer: D) -> Result<HashMap<u32, bool>, D::Error>
where
D: Deserializer<'de>,
{
let str_map = HashMap::<&str, bool>::deserialize(deserializer)?;
let original_len = str_map.len();
let data = {
str_map
.into_iter()
.map(|(str_key, value)| match str_key.parse() {
Ok(int_key) => Ok((int_key, value)),
Err(_) => Err({
de::Error::invalid_value(
de::Unexpected::Str(str_key),
&"a non-negative integer",
)
}),
}).collect::<Result<HashMap<_, _>, _>>()?
};
// multiple strings could parse to the same int, e.g "0" and "00"
if data.len() < original_len {
return Err(de::Error::custom("detected duplicate integer key"));
}
Ok(data)
}
1 Like
jonah
October 3, 2019, 6:15pm
4
Thanks! That's about what I was expecting... it's a bit heavier than what I was hoping for
I really appreciate you writing it out for me, because the type and bounds of the deserialize_data
function would have given me some headaches!
system
Closed
January 1, 2020, 6:15pm
5
This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.