How to deserialize dotted fields with serde

Basically I want to deserialize

{
   "a": 1,
   "b": "hello",
   "fields.a.f1": "v1",
   "fields.a.f2": "v2",
   "fields.b.f1": "v3",
   "fields.b.f2": "v4"
}

to such struct

{  
    "a": 1,
    "b": "hello",
    "fields": {
       "a": { "f1": "v1", "f2": "v2" },
       "b": { "f1": "v3", "f2": "v4" }
   }
}
pub struct Foo {
    a: usize,
    b: String,
    fields: HashMap<String, String>,
}

Since I only want customize the behavior for fields, is it possible to achieve this by some macros like #[serde(deserialize_with)? Or do I have to manually implement Deserialize for the whole struct?

How do you like this? Playground.

3 Likes

Here is an allocation-free, more idiomatic, and more generic version, which doesn't crash on incorrectly-formatted keys.

6 Likes

Cool! I didn't come up with using flatten and deserialize_with together.

A follow-up question: I want to make fields strongly-typed. i.e., fields: HashMap<String, Properties>, where Properties is a struct. So that I can reject unknown properties. What's the most idiomatic way to do this? :thinking:

The most straightforward way is still first deserialize to fields: HashMap<String, HashMap<String, String> though.

Another way I came up with is to define a Builder trait for the properties, so that the Properties can be built incrementally. But I don't know whether this can be achieved by serde traits, like a de::Visitor? IMO this is like we only visit one field at a time, and finally finish the visit_map.

I like this idea.

How about something like this? (based on @H2CO3's implementation from above)

This aligns with the implementation in my head.

I'm just wondering whether we can do sth like #[derive(Deserialize) on Properties and then use some serde scaffold to build them in a general way.

Anyway, I would probably go with this approach, instead of seeking for generalization too early. :smile:

You can achieve that with a #[derive] macro, but I wouldn't necessarily bother with it. A general solution (albeit one that allocates a temporary map) would be to use e.g. serde_value, like this.

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.