serde_with icon indicating copy to clipboard operation
serde_with copied to clipboard

Create a proc-macro attribute which applies a second attribute on all fields of a type.

Open jonasbb opened this issue 5 years ago • 3 comments

Serialization code is often repetitive. One helper to improve on this already exists in this crate, the serde_with::skip_serializing_none attribute.

However, this could be generalized to work with any attribute on a user-specified type (list). This could look like

#[apply(Vec, HashMap => #[serde(skip_serializing_if = "$TYPE::is_empty"])]
struct {}

This would apply the attribute on all fields of type Vec and HashMap. On the right hand side, the placeholder $TYPE would be replaced by the type of the field. This would make it easier to skip serializing all empty container types.

serde_with::skip_serializing_none could in the end be implemented on top of this more general proc-macro.

jonasbb avatar Feb 21 '20 23:02 jonasbb

A supplemental use-case for this is a companion to skip_serializing_none: APIs which need that (e.g. JSON swagger / openapi) tend to need skip_serializing_if = "Option::is_none" on the serialization side, but also default on the deserialization side.

skip_serializing_none only covers the first one, so when one needs to handle both sides of the serialization (sometimes on the same struct e.g. because it's defined for both the client and server to use) it can be a bit frustrating.

Possible issues with this:

  • does serde allow multple #[serde] annotations per field? What if a user needs both a blanket annotation, and a specific annotation on a specific field (e.g. rename)?
  • what about reverting the behaviour for one specific field? e.g. if the user wants to bulk-apply "skip empty collections" but one or two of them must be serialised empty? Or an API which mixes optional fields (skipped) and required nullable fields (not skipped)?

I guess the macro could just bail (refuse to compile) if it sees a #[serde] annotation on a field it tries to configure, seems safer than trying to mess around even if it means the user is back to hand-rolling the entire thing.

xmo-odoo avatar May 30 '22 10:05 xmo-odoo

A supplemental use-case for this is a companion to skip_serializing_none: APIs which need that (e.g. JSON swagger / openapi) tend to need skip_serializing_if = "Option::is_none" on the serialization side, but also default on the deserialization side.

You do not need default on a field of type Option. Missing fields automatically default to None. The exception is if you use #[serde(with = ...)] or #[serde_as(...)]. In the first case using default changes the meaning, and the second case is tracked here #185.

  • does serde allow multple #[serde] annotations per field? What if a user needs both a blanket annotation, and a specific annotation on a specific field (e.g. rename)?

Multiple annotations work flawlessly. Their individual meanings get joined.

  • what about reverting the behaviour for one specific field? e.g. if the user wants to bulk-apply "skip empty collections" but one or two of them must be serialised empty? Or an API which mixes optional fields (skipped) and required nullable fields (not skipped)?

Similar to skip_serializing_none a simple #[no_apply] probably makes sense. It should mark the field to be ignored for any apply macro. Having two #[apply] apply on one type is probably rare, and even rarer that they overlap but you only want to opt-out of one apply. In such corner cases, all attributes can always be applied manually on the fields.

I guess the macro could just bail (refuse to compile) if it sees a #[serde] annotation on a field it tries to configure, seems safer than trying to mess around even if it means the user is back to hand-rolling the entire thing.

I would not want the apply macro to parse any annotations not meant for it. This means if there is already a #[serde] on the field, that shouldn't bother the apply macro. If any other macros or derives cannot handle that, then that is a user/other crate problem. The reason here is simply that the annotation to be applied on the field does not have to be serde related. It could also be for any other derive. So erroring, if there is a #[serde] annotation, is wrong.

jonasbb avatar May 30 '22 15:05 jonasbb

A supplemental use-case for this is a companion to skip_serializing_none: APIs which need that (e.g. JSON swagger / openapi) tend to need skip_serializing_if = "Option::is_none" on the serialization side, but also default on the deserialization side.

You do not need default on a field of type Option. Missing fields automatically default to None.

Well now I'm astonished, I was convinced it'd yield a parsing error.

That is rather convenient, thank you very much.

xmo-odoo avatar May 31 '22 06:05 xmo-odoo

This is available since a bit as serde_with::apply.

jonasbb avatar May 03 '23 19:05 jonasbb