How to use serde to deserialize toml key as u32?

Hello!

I have a TOML config file which holds pairs of u32s and bools. (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.

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.

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

Thanks! That's about what I was expecting... it's a bit heavier than what I was hoping for :smiley:

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!

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.