0

I would like to parse the following part of a .yaml-file:

networks:
  foo1: 192.168.1.0/24
  bar1: 192.168.2.0/24
  foo2: 2001:CAFE:1::/64
  bar2: 2001:CAFE:2::/64

... where the key of each property is the labels the name of a network and the value indicates the assigned subnet to it. The deserialized structs should look like the following:

#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Networks {
    networks: Vec<Network>
}

#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Network {
    name: String,
    subnet: String,
}

I would prefer this notation over

networks:
  - name: foo1
    subnet: 192.168.1.0/24
  - ...

since this would add unnecessary boilerplate. The problem is that I cannot properly parse this list of networks into structs, since to my current knowledge, the property of a struct has to have an equivalent key to it - and since the key-names here are "random" I cannot do that.

The only other workaround I've found is parsing the entries as HashMaps and automatically convert those into tuples of (String, String) (networks would then be a Vec<(String, String)>) via the serde-tuple-vec-map crate (which loses the named parameters).

This seems like something that would be easy to configure, yet I haven't found any solutions elsewhere.

  • This is theoretically possible but requires a custom deserializer for the `Vec`, unfortunately. – cdhowie Jul 12 '22 at 00:30

1 Answers1

2

Implementing a custom deserializer and using it with #[serde(deserialize_with = "network_tuples")] networks: Vec<Network> is one way of doing it:

fn network_tuples<'de, D>(des: D) -> Result<Vec<Network>, D::Error>
where
    D: serde::Deserializer<'de>,
{
    struct Vis(Vec<Network>);
    impl<'de> serde::de::Visitor<'de> for Vis {
        type Value = Vec<Network>;
        fn expecting(&self, _formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
            todo!("return nice descriptive error")
        }

        fn visit_map<A>(mut self, mut map: A) -> Result<Self::Value, A::Error>
        where
            A: serde::de::MapAccess<'de>,
        {
            while let Some((name, subnet)) = map.next_entry()? {
                self.0.push(Network { name, subnet });
            }
            Ok(self.0)
        }
    }

    des.deserialize_map(Vis(vec![]))
}

You could do without the visitor by first deserializing to a HashMap<String, String> and then converting, but that has a performance penalty and you'd lose the ability to have two networks with the same name (not sure if you want that though).

Playground


Of course, you could also combine my favorite serde trick #[serde(from = "SomeStructWithVecStringString")] with the serde-tuple-vec-map crate. But I've already written many answers using from, so I'll refer to an example.

Caesar
  • 6,733
  • 4
  • 38
  • 44
  • (Different question, but the [answer](https://stackoverflow.com/questions/64405600/how-can-i-deserialize-json-that-has-duplicate-keys-without-losing-any-of-the-val/64406395#64406395=) is rather similar.) – Caesar Jul 12 '22 at 14:48