I thought this was an interesting question/challenge so I wrote a simple proc-macro attribute to do this called serde_flat_path
. Here is an example of how it can be used to provide the functionality described in the question:
#[serde_flat_path::flat_path]
#[derive(Serialize, Deserialize)]
struct Root {
#[flat_path(path = ["f1", "f2", "f3"])]
f3: u64,
}
The attribute must be placed before deriving Serialize
or Deserialize
since it will place serde
attributes on fields with #[flat_path(...)]
. I have attempted to make sure that this attribute plays well with other serde attributes and helper crates as much as possible. It can also be used for more complex types like the one below. To a Serializer
or Deserializer
it should appear no different from actually writing out all of the structs in the chain. For the specifics, you can check out the crate's readme.
#[serde_flat_path::flat_path]
#[derive(Serialize, Deserialize)]
#[serde(tag = "foobar")]
pub enum Bar {
Foo {
#[flat_path(path = ["a", "b", "c", "d", "e"])]
#[serde(with = "flip_bool")]
foo: bool,
#[flat_path(path = ["x", "y", "z"])]
#[serde(skip_serializing_if = "Option::is_some")]
x: Option<u64>,
},
Bar {
#[flat_path(path = ["a", "b"])]
bar: Bar,
},
Baz,
Biz {
#[serde(with = "add_one")]
z: f64,
}
}
Fair warning though as this proc-macro is not perfect. At the moment it can not handle overlapping flattened paths due to the way the macro is expanded. If this is attempted, a compile time error will be emitted unless you use the allow_overlap
feature. It also struggles with generics in some cases, but I am looking to improve this.
// This would produce an error since x and y have overlapping paths
#[serde_flat_path::flat_path]
#[derive(Serialize, Deserialize)]
struct Foo {
z: bool,
#[flat_path(path = ["a", "b", "x"])]
x: u64,
#[flat_path(path = ["a", "c", "y"])]
y: u64,
}
let foo = Foo { z: true, x: 123, y: 456 };
println!("{}", serde_json::to_string(&foo).unwrap());
// Output:
// {"z":true,"a":{"b":{"x":123}},"a":{"c":{"y":456}}}
serde(flatten)
which AFAIK only works the other way around (though it's possible that at one point it also discussed flattening nested json). – Loramjson_dotpath
crate could make life much easier. – Shingle