Serde: overwriting child struct's #[serde(rename_all = "camelCase")]

I have a child and parent structs like so:

#[serde(rename_all = "camelCase")]
pub struct Child {
    some_field: u64,
}

#[serde(rename_all = "snake_case")]
pub struct Parent {
    child: Child,
}

I want both of them to be snake_case.

The child struct is defined in an imported library, so I can't change it, and I'd rather not duplicate it.

Is it possible to void / overwrite the camelCase macro on top of the child struct? Adding snake_case on top of parrent doesn't seem to work.


Solution 1:

I don't think this is possible to achieve through some nice attributes.


It is doable in the general case, but I really think you shouldn't:

  1. You can tell serde to use your custom function to serialize members via serialize_with:
#[derive(Debug,Serialize)]
#[serde(rename_all = "snake_case")]
pub struct Parent {
    #[serde(serialize_with = "serialize_rename")]
    child: Child,
}
  1. You can then implement the custom serialization function to redirect to a custom Serializer.
struct RenamingSerializer<S>(S);

fn serialize_rename<T: Serialize, S: Serializer>(val: &T, ser: S) -> Result<S::Ok, S::Error> {
    val.serialize(RenamingSerializer(ser))
}
  1. Why would you do that? Because serializers are essentially just visitors for the data structure being serialized. So when you happen to "visit" a struct, you can redirect to a different method for serializing struct fields:
struct RenameSerializeStruct<S>(S);

impl<S: Serializer> Serializer for RenamingSerializer<S> {
    type SerializeStruct = RenameSerializeStruct<S::SerializeStruct>;
    fn serialize_struct(
        self,
        name: &'static str,
        len: usize,
    ) -> Result<Self::SerializeStruct, Self::Error> {
        Ok(RenameSerializeStruct(self.0.serialize_struct(name, len)?))
    }
    // snip all the other stuff you need to impl
}
  1. The SerializeStruct is another "visitor", and it'll see all the fields of a struct.
impl<S: SerializeStruct> SerializeStruct for RenameSerializeStruct<S> {
    type Ok = S::Ok;
    type Error = S::Error;

    fn serialize_field<T: ?Sized>(
        &mut self,
        key: &'static str,
        value: &T,
    ) -> Result<(), Self::Error>
    where T: Serialize {
        /// change key to the case you want, and call serialize_field on .0
    }

    fn end(self) -> Result<Self::Ok, Self::Error> {
        self.0.end()
    }
}
  1. Now, if you've paid attention (I haven't), you noticed that we need to forward the key and value to S::serialize_field, but that expects a &'static str. If we construct a new String here, it won't live long enough. Well, there's no perfect solution for this, you could use a pool to internalize strings, I decided to memoize them in a map (since all the key strings existed at compile time, I won't worry about it growing large):
lazy_static::lazy_static! {
    // Probably not so good if your thing is multithreaded.
    // (There are of course further overengineered solutions for that.)
    static ref NAMES: Mutex<HashMap<&'static str, &'static str>> = Default::default();
}
  1. Finally, you can convert the cases of struct field keys:
let key = {
    let mut names = NAMES.lock().expect("poisoned and dead");
    use std::collections::hash_map::Entry::*;
    match names.entry(key) {
        Occupied(e) => *e.get(),
        Vacant(e) => {
            use convert_case::*;
            let key: &str = Box::leak(Box::new(key.to_case(Case::Snake)));
            e.insert(key);
            key
        },
    }
};
self.0.serialize_field(key, value)

I've left out quite a bit of the boilerplate, so here's a runable Playground.


Now, the above solution doesn't do this recursively, but only for one level. If you wanted to apply to all descendants, I think that is possible by defining a wrapper struct, marking it #[serde(transparent, serialize_with = "serialize_rename")], and wrapping all values in calls like serialize_field with this wrapper struct.


In any case, if you'd have to be crazy to go for the solution above, or be wanting to treat hundreds of fields on dozens of structs at least.

If you're willing to specialize for Child, there's a much saner solution:

#[derive(Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct Parent {
    #[serde(serialize_with = "serialize_rename")]
    child_field: Child,
}

fn serialize_rename<S: Serializer>(val: &Child, ser: S) -> Result<S::Ok, S::Error> {
    let Child { some_field } = val;
    let some_field = *some_field;
    #[derive(Serialize)]
    #[serde(rename_all = "snake_case")]
    struct SnakeChild {
        some_field: u64,
    }
    SnakeChild { some_field }.serialize(ser)
}

Playground