Deserialize and serialize struct to map with a single field

I have a struct that has a variable key that i need to serialize and de serialize to json.

Currently I am using hashmap but i would like to move it DesiredImplementation approach.

Because hashmap can de serialize to empty hashmap, and then using hashmap its not convenient when i not there should be only one entry inside.

How can I achieve that?

use serde::Serialize;
use serde_json;
use std::collections::HashMap;

#[derive(Debug, Serialize)]
struct CurrentImplementation<'a> {
    #[serde(flatten)]
    field: HashMap<&'a str, &'a str>,
}

struct DesiredImplementation<'a> {
    key: &'a str,
    value: &'a str
}

impl <'a>CurrentImplementation<'a> {
    fn new(key: &'a str, value: &'a str) -> Self {
        let mut map = HashMap::with_capacity(1);
        map.insert(key, value);
        return Self {field: map}
    }
}

fn main() {
    let example = CurrentImplementation::new("example", "example");
    println!("{:#?}", serde_json::to_string_pretty(&example));
}

Just write a manual impl:

impl Serialize for DesiredImplementation<'_> {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        let mut map = serializer.serialize_map(Some(1))?;
        map.serialize_entry(self.key, self.value)?;
        map.end()
    }
}

Playground

This is a tricky case, because "Map" types are handled specially in serde. You may have to implement Deserialize yourself:

https://serde.rs/deserialize-map.html

BTW: don't put temporary scope-bound loans in structs &str. This forbids the structs from storing the data, and instead makes them views into data kept in some variable, and will cause you endless problems with the borrow checker. In some cases it's literally impossible to deserialize JSON into &str. String is the correct type for storing strings in structs.

Thanks that works great for serializing it.

Currently I am working on the de serialize part, if i'll make that work i'll post it here for future reference.

EDIT:
Currently stuck at this

impl<'de> serde::Deserialize<'de> for DesiredImplementation<'_> {
    fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
        let (key, value) = HashMap::deserialize(d)?.iter().next().unwrap();
        return Ok(Self {key: key, value: value})
    }
}

You can't deserialize into a reference. There would be no owner to hold the value. You can use a Cow to dynamically switch between owned and borrowed data.

I don't know if i should create another issue but i have troubles converting your implementation to generic value

impl<'de, T: Deserialize<'de>> Visitor<'de> for KeyValueVisitor where T: Deserialize<'de> {
    type Value = KeyValueStruct<'de, T>;
    
    fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
        f.write_str("a map with exactly one key-value pair")
    }

    fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
    where
        M: MapAccess<'de>,
    {
        match access.next_entry()? {
            Some((k, v)) => Ok(KeyValueStruct { key: k, value: v }),
            None => Err(serde::de::Error::invalid_length(
                access.size_hint().unwrap_or(0),
                &self,
            )),
        }
    }
}
the type parameter `T` is not constrained by the impl trait, self type, or predicates

The type of the visitor or the trait has to depend on the type parameter, otherwise there would be overlapping impls (many different impls of the same trait on the same type for each choice of T).

Since in your case, it doesn't make sense for the trait to depend on the type (Deserialize is not generic over a type), the only possibility is to introduce a type parameter for the Visitor, that carries over the type information to the impl:

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.