Deserialize and validate JSON array with config values

Hi! I need to parse a JSON array containing config values and then afterwards I need to validate those values. A value cannot be validated by itself, but sometimes a value is only valid if another value is within a certain range. The values are "simple" types like: u32, f32, u8, bool and u8 arrays of known size e.g. [u8; 6]. Depending on the given ID it is known which type it shall be.

An example of the JSON data is here:

[
    {
        "size": 4,
        "id": "1",
        "value": 864000000
    },
    {
        "size": 2,
        "id": "2",
        "value": 200
    },
    {
        "size": 6,
        "id": "3",
        "value": [1,2,3,4,5,6]
    },
]

I already solved parsing with serde by using an array and the rename attribute as follows:

#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(tag = "id", content = "value")]
pub enum ConfigValue {
    #[serde(rename = "1")]
    One(u32),

    #[serde(rename = "2")]
    Two(u16),

    #[serde(rename = "3")]
    Three([u8; 6]),
}
#[derive(Serialize, Deserialize, Debug)]
pub struct ConfigEntry {
    size: u8,
    #[serde(flatten)]
    value: config_value::ConfigValue,
}

type ConfigEntries = Vec<ConfigEntry>;

Now I can parse the input JSON to a list of ConfigEntry. However, if I want to get the entry of a certain ID, I know have to traverse the whole list and search for the entry. Then I need to unwrap the enum via match. This feels a little clumpsy. I was thinking about storing a ConfigEntry within a hashmap, where the key of the map would be the ID. But still I would need to match the enum. Furthermore, it would be nice if I could access the value directly like if I had a struct.

I found some crates which might be useful:
https://crates.io/crates/enum-as-inner
https://crates.io/crates/enum-map

As already mentionend, I already have a working solution, but the solution just does not feel ergonomic. How would you model something like this or what would be the recommended way to store such data?

Thank you for your help! :slight_smile:

I would do it in two steps. First, your solution is already idiomatic since modeling your constraints with an enum is the correct thing to do. Next, to make it more ergonomic, I would simply map the deserialized array into a HashMap<u32, ConfigValue> like this:

let config_entries: Vec<ConfigEntry> = ... // The deserialized config Vec that you already have.
let config_entries_map = config_entries.into_iter().map(|config_entry| /* Do the mapping */).collect::<HashMap<u32, ConfigValue>>();
2 Likes

Thanks for your feedback!
Do you have any suggestion how to connect the u32 properly with the other enum? Of course I could add a second enum just for mapping the u32, but it would be nice to just manage the data within one spot.

I'm not sure what do you mean exactly but, as I said, I would levae the deserialization solution as is. Mapping the Vector into a HashMap is just for ergonomic/performance reasons, and having the solution split into two parts makes it easy both to understand and maintain.

Don't overengineer your solutions. Keep them simple.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.