jonasbb / serde_with

This crate provides custom de/serialization helpers to use in combination with serde's `with`-annotation and with the improved `serde_as`-annotation.
https://docs.rs/serde_with
Apache License 2.0
666 stars 72 forks source link

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

Closed jonasbb closed 1 year ago

jonasbb commented 4 years ago

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.

xmo-odoo commented 2 years ago

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:

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.

jonasbb commented 2 years ago

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.

xmo-odoo commented 2 years ago

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.

jonasbb commented 1 year ago

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