1

Here is a Rust playground link with what I have so far.

I am working on a Rust project where the serialization/deserialization MUST be interoperable with a C# project. I have these two structs that are approximations of my C# classes to outline my case:

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct MyStruct<'a> {
    #[serde(flatten)]
    #[serde(borrow)]
    other_struct_map: HashMap<&'a str, MyOtherStruct<'a>>
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct MyOtherStruct<'a> {
    #[serde(flatten)]
    #[serde(borrow)]
    data: HashMap<&'a str, &'a str>
}

I have been using serde(flatten) because it gets very close to what I want (and because HashMaps don't generically implement Deserialize/Serialize), but it is not correct. My Rust playground result takes these instances of my structs:

let mut map = HashMap::new();
map.insert("key1", "value1");
map.insert("key2", "value2");
let other = MyOtherStruct { data: map };
let mut map2 = HashMap::new();
map2.insert("key1", other);
let myStruct = MyStruct { other_struct_map: map2 };

And serializes it this way:

{
    "key1": {
        "key2": "value2",
        "key1": "value1"
    }
}

But this is not how C# would want this. It would serialize with the field names kept, so like this:

{
    "otherStructMap": {
        "data": {
            "key1": {
                "key2": "value2",
                "key1": "value1"
            }
        }
    }
}

I tried to use serialize_with and MapSerializer to create a HashMap<&str, _> approach and it compiles but it produces the exact same results as flatten: Rust Playground. I suspect this is close to a working solution if I knew how to make sure what I serialize_map is wrapped in another json node. I tried this because an important part of my solution is it must be generic for any HashMap<&str, _> because I have multiple such HashMaps and it would be ideal if I had one function that works for them all.

Thanks a lot for the help

1 Answer 1

1

I made a dumb mistake. In the second Rust Playground link I sent, it showed a different approach that produced the exact same thing as flatten... But it was because I kept the flatten attributes... Removing that it works exactly as I wanted:

use serde::{Deserialize, Serialize};
use std::collections::HashMap;

#[derive(Serialize, Deserialize, Debug)]
struct MyStruct<'a> {
    #[serde(borrow)]
    #[serde(with = "map_serialize")]
    other_struct_map: HashMap<&'a str, MyOtherStruct<'a>>
}

#[derive(Serialize, Deserialize, Debug)]
struct MyOtherStruct<'a>
{
    #[serde(borrow)]
    #[serde(with = "map_serialize")]
    data: HashMap<&'a str, &'a str>
}

mod map_serialize {
    use serde::{Deserialize, Deserializer, Serialize, Serializer};
    use serde::ser::SerializeMap;
    use std::collections::HashMap;

    pub fn serialize<'a, V, S>(target: &'a HashMap<&'a str, V>, ser: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
        V: Serialize + 'a,
    {
        let mut map = ser.serialize_map(Some(target.len()))?;
        for (k, v) in target {
            map.serialize_entry(k, v)?;
        }
        map.end()
    }

    pub fn deserialize<'de, V, D>(des: D) -> Result<HashMap<&'de str, V>, D::Error>
    where
        D: Deserializer<'de>,
        V: Deserialize<'de>
    {
        serde::Deserialize::deserialize(des)
    }
}

It deserializes my instance exactly as I wanted:

{
    "other_struct_map": {
        "key1": {
            "data": {
                "key2": "value2",
                "key1": "value1"
            }
        }
    }
}

Rust Playground

2
  • 1
    The map_serialize module has no effect on the serialized data. You get the same result with the normal implementation for HashMap.
    – jonasbb
    Commented Jul 9 at 12:58
  • Sorry normal implementation? I looked for this approach because serde said HashMap doesn't implement Serialize/Deserialize, so I needed to implement. Do you mean there'd be some kind of simpler implementation for serialize, something that just calls something like ser.serialize(target) and that would've worked and I didn't need to use serialize_map? Commented Jul 9 at 22:26

Not the answer you're looking for? Browse other questions tagged or ask your own question.