Serde custom Serializer that abstracts object layout

Hello. The code at the bottom of the post has the struct "CompoundStruct" that is composed of two other structs. The code, as it's written, serializes into (in json):

{
  "struct_a": {
    "field_1": 1,
    "field_2": 2
  },
  "struct_b": {
    "field_3": 3,
    "field_4": 4
  }
}

But I want it to serialize into:

{
  "field_1": 1,
  "field_2": 2,
  "field_3": 3,
  "field_4": 4
}

Obviously I could reach inside struct_a and struct_b inside my serialize function for CompoundStruct, but I want to keep StructA and StructB as opaque as possible from the point of view of CompoundStruct. I don't mind if StructA and StructB have an awareness that they are serializing into a map, or a specialized function to serialize them into a map.

What do you suggest, that would be most in-line with the serde way of thinking?

Thanks in advance!

extern crate serde;
use crate::serde::Serialize;
use crate::serde::ser::{SerializeMap};
extern crate serde_json;
use crate::serde_json::json;

#[derive(Serialize)]
pub struct StructA {
    field_1 : u32,
    field_2 : u32
}

#[derive(Serialize)]
pub struct StructB {
    field_3 : u32,
    field_4 : u32
}

pub struct CompoundStruct {
    struct_a : StructA,
    struct_b : StructB
}

impl serde::ser::Serialize for CompoundStruct {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where S: serde::ser::Serializer {

        let mut map = serializer.serialize_map(None)?;

        map.serialize_entry("struct_a", &self.struct_a)?;
        map.serialize_entry("struct_b", &self.struct_b)?;
        
        map.end()
    }
}

fn main() {

let my_struct = CompoundStruct {
    struct_a : StructA{ field_1 : 1, field_2 : 2},
    struct_b : StructB{ field_3 : 3, field_4 : 4},
};

let my_json = json!(my_struct);
println!("{}", serde_json::to_string_pretty(&my_json).unwrap());
}

https://serde.rs/attr-flatten.html is likely what you want.

Thank you very much for the reply!

#[serde(flatten)]

Is definitely on the right track. However, it appears to work only for derived Serializers. For other reasons, I need to write a custom serializer function for CompoundStruct.

I'm not familiar enough with Rust to know if this is possible, but can Rust be made to dump out the derived functions so I can inspect them? Basically I want to see if it's calling some other serde interface, e.g. map.serialize_flattened_entries()

Thanks again for the reply.

You can inspect the generated code using cargo expand. Write a minimal example using flatten and then see the generated code.

Sweet! Thanks for the tip!

After inspecting the derive-generated function, I have the answer. Sure enough, as suspected, it's a private serde interface: FlatMapSerializer.

Here's the new serialize function, utilizing FlatMapSerializer

impl serde::ser::Serialize for CompoundStruct {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where S: serde::ser::Serializer {

        let mut map = serializer.serialize_map(None)?;

        self.struct_a.serialize(serde::private::ser::FlatMapSerializer(&mut map))?;
        self.struct_b.serialize(serde::private::ser::FlatMapSerializer(&mut map))?;
        
        map.end()
    }
}

Yesterday, digging through the serde source, I found FlatMapSerializer, but I didn't know the right way to use it until now.

Thanks again to everyone for pointing me in the right directions. I've learned a ton about the design of serde and Rust macros over the last day.

Note that this solution might break unexpectedly on minor Serde update, since this interface isn't really a part of public API (it is accessible only due to the fact that macros cannot reach private items). If you're creating an application and not a library, you might want to pin the version in Cargo.toml, by stating it as =1.x instead of 1.x.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.