diff --git a/serde_with/tests/serde_as/serde_as_macro.rs b/serde_with/tests/serde_as/serde_as_macro.rs index b180079d..8ef8fcb6 100644 --- a/serde_with/tests/serde_as/serde_as_macro.rs +++ b/serde_with/tests/serde_as/serde_as_macro.rs @@ -189,3 +189,119 @@ fn test_serde_as_macro_multiple_field_attributes() { }"#]], ); } + +/// Ensure that `serde_as` applies `default` if both the field and the conversion are option. +#[test] +fn test_default_on_option() { + #[serde_as] + #[derive(Debug, Eq, PartialEq, Serialize, Deserialize)] + struct Data { + #[serde_as(as = "Option")] + a: Option, + } + + is_equal( + Data { a: None }, + expect![[r#" + { + "a": null + }"#]], + ); + is_equal( + Data { a: Some(123) }, + expect![[r#" + { + "a": "123" + }"#]], + ); + check_deserialization(Data { a: None }, "{}"); + + #[serde_as] + #[derive(Debug, Eq, PartialEq, Serialize, Deserialize)] + struct DataNoDefault { + #[serde_as(as = "Option", no_default)] + a: Option, + } + + is_equal( + DataNoDefault { a: None }, + expect![[r#" + { + "a": null + }"#]], + ); + is_equal( + DataNoDefault { a: Some(123) }, + expect![[r#" + { + "a": "123" + }"#]], + ); + check_error_deserialization::( + "{}", + expect!["missing field `a` at line 1 column 2"], + ); + + fn default_555() -> Option { + Some(555) + } + + #[serde_as] + #[derive(Debug, Eq, PartialEq, Serialize, Deserialize)] + struct DataExplicitDefault { + #[serde_as(as = "Option")] + #[serde(default = "default_555")] + a: Option, + } + + is_equal( + DataExplicitDefault { a: None }, + expect![[r#" + { + "a": null + }"#]], + ); + is_equal( + DataExplicitDefault { a: Some(123) }, + expect![[r#" + { + "a": "123" + }"#]], + ); + check_deserialization(DataExplicitDefault { a: Some(555) }, "{}"); + + #[serde_as] + #[derive(Debug, Eq, PartialEq, Serialize, Deserialize)] + struct DataString { + #[serde_as(as = "NoneAsEmptyString")] + a: Option, + } + + is_equal( + DataString { a: None }, + expect![[r#" + { + "a": "" + }"#]], + ); + is_equal( + DataString { + a: Some("123".to_string()), + }, + expect![[r#" + { + "a": "123" + }"#]], + ); + check_deserialization(DataString { a: None }, r#"{"a": ""}"#); + check_deserialization( + DataString { + a: Some("555".to_string()), + }, + r#"{"a": "555"}"#, + ); + check_error_deserialization::( + "{}", + expect!["missing field `a` at line 1 column 2"], + ); +} diff --git a/serde_with_macros/src/lib.rs b/serde_with_macros/src/lib.rs index 912af4df..e74586c8 100644 --- a/serde_with_macros/src/lib.rs +++ b/serde_with_macros/src/lib.rs @@ -45,7 +45,10 @@ extern crate proc_macro; mod utils; use crate::utils::{split_with_de_lifetime, DeriveOptions, IteratorExt as _}; -use darling::{util::Override, Error as DarlingError, FromField, FromMeta}; +use darling::{ + util::{Flag, Override}, + Error as DarlingError, FromField, FromMeta, +}; use proc_macro::TokenStream; use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::quote; @@ -294,52 +297,47 @@ pub fn skip_serializing_none(_args: TokenStream, input: TokenStream) -> TokenStr /// Add the skip_serializing_if annotation to each field of the struct fn skip_serializing_none_add_attr_to_field(field: &mut Field) -> Result<(), String> { - if let Type::Path(path) = &field.ty { - if is_std_option(&path.path) { - let has_skip_serializing_if = - field_has_attribute(field, "serde", "skip_serializing_if"); - - // Remove the `serialize_always` attribute - let mut has_always_attr = false; - field.attrs.retain(|attr| { - let has_attr = attr.path.is_ident("serialize_always"); - has_always_attr |= has_attr; - !has_attr - }); - - // Error on conflicting attributes - if has_always_attr && has_skip_serializing_if { - let mut msg = r#"The attributes `serialize_always` and `serde(skip_serializing_if = "...")` cannot be used on the same field"#.to_string(); - if let Some(ident) = &field.ident { - msg += ": `"; - msg += &ident.to_string(); - msg += "`"; - } - msg += "."; - return Err(msg); - } + if is_std_option(&field.ty) { + let has_skip_serializing_if = field_has_attribute(field, "serde", "skip_serializing_if"); - // Do nothing if `skip_serializing_if` or `serialize_always` is already present - if has_skip_serializing_if || has_always_attr { - return Ok(()); - } + // Remove the `serialize_always` attribute + let mut has_always_attr = false; + field.attrs.retain(|attr| { + let has_attr = attr.path.is_ident("serialize_always"); + has_always_attr |= has_attr; + !has_attr + }); - // Add the `skip_serializing_if` attribute - let attr = parse_quote!( - #[serde(skip_serializing_if = "Option::is_none")] - ); - field.attrs.push(attr); - } else { - // Warn on use of `serialize_always` on non-Option fields - let has_attr = field - .attrs - .iter() - .any(|attr| attr.path.is_ident("serialize_always")); - if has_attr { - return Err( - "`serialize_always` may only be used on fields of type `Option`.".into(), - ); + // Error on conflicting attributes + if has_always_attr && has_skip_serializing_if { + let mut msg = r#"The attributes `serialize_always` and `serde(skip_serializing_if = "...")` cannot be used on the same field"#.to_string(); + if let Some(ident) = &field.ident { + msg += ": `"; + msg += &ident.to_string(); + msg += "`"; } + msg += "."; + return Err(msg); + } + + // Do nothing if `skip_serializing_if` or `serialize_always` is already present + if has_skip_serializing_if || has_always_attr { + return Ok(()); + } + + // Add the `skip_serializing_if` attribute + let attr = parse_quote!( + #[serde(skip_serializing_if = "Option::is_none")] + ); + field.attrs.push(attr); + } else { + // Warn on use of `serialize_always` on non-Option fields + let has_attr = field + .attrs + .iter() + .any(|attr| attr.path.is_ident("serialize_always")); + if has_attr { + return Err("`serialize_always` may only be used on fields of type `Option`.".into()); } } Ok(()) @@ -352,12 +350,39 @@ fn skip_serializing_none_add_attr_to_field(field: &mut Field) -> Result<(), Stri /// * `Option` /// * `std::option::Option`, with or without leading `::` /// * `core::option::Option`, with or without leading `::` -fn is_std_option(path: &Path) -> bool { - (path.leading_colon.is_none() && path.segments.len() == 1 && path.segments[0].ident == "Option") - || (path.segments.len() == 3 - && (path.segments[0].ident == "std" || path.segments[0].ident == "core") - && path.segments[1].ident == "option" - && path.segments[2].ident == "Option") +fn is_std_option(type_: &Type) -> bool { + match type_ { + Type::Array(_) + | Type::BareFn(_) + | Type::ImplTrait(_) + | Type::Infer(_) + | Type::Macro(_) + | Type::Never(_) + | Type::Ptr(_) + | Type::Reference(_) + | Type::Slice(_) + | Type::TraitObject(_) + | Type::Tuple(_) + | Type::Verbatim(_) => false, + + Type::Group(syn::TypeGroup { elem, .. }) + | Type::Paren(syn::TypeParen { elem, .. }) + | Type::Path(syn::TypePath { + qself: Some(syn::QSelf { ty: elem, .. }), + .. + }) => is_std_option(elem), + + Type::Path(syn::TypePath { qself: None, path }) => { + (path.leading_colon.is_none() + && path.segments.len() == 1 + && path.segments[0].ident == "Option") + || (path.segments.len() == 3 + && (path.segments[0].ident == "std" || path.segments[0].ident == "core") + && path.segments[1].ident == "option" + && path.segments[2].ident == "Option") + } + _ => false, + } } /// Determine if the `field` has an attribute with given `namespace` and `name` @@ -527,6 +552,7 @@ fn serde_as_add_attr_to_field( r#as: Option, deserialize_as: Option, serialize_as: Option, + no_default: Flag, } impl SerdeAsOptions { @@ -543,6 +569,7 @@ fn serde_as_add_attr_to_field( serialize_with: Option, borrow: Option>, + default: Option>, } impl SerdeOptions { @@ -551,6 +578,33 @@ fn serde_as_add_attr_to_field( } } + /// Emit a `borrow` annotation, if the replacement type requires borrowing. + fn emit_borrow_annotation(serde_options: &SerdeOptions, as_type: &Type, field: &mut Field) { + let type_borrowcow = &syn::parse_quote!(BorrowCow); + // If the field is not borrowed yet, check if we need to borrow it. + if serde_options.borrow.is_none() && has_type_embedded(as_type, type_borrowcow) { + let attr_borrow = parse_quote!(#[serde(borrow)]); + field.attrs.push(attr_borrow); + } + } + + /// Emit a `default` annotation, if `as_type` and `field` are both `Option`. + fn emit_default_annotation( + serde_as_options: &SerdeAsOptions, + serde_options: &SerdeOptions, + as_type: &Type, + field: &mut Field, + ) { + if !serde_as_options.no_default.is_present() + && serde_options.default.is_none() + && is_std_option(as_type) + && is_std_option(&field.ty) + { + let attr_borrow = parse_quote!(#[serde(default)]); + field.attrs.push(attr_borrow); + } + } + // Check if there even is any `serde_as` attribute and exit early if not. if !field .attrs @@ -583,27 +637,20 @@ fn serde_as_add_attr_to_field( } let type_same = &syn::parse_quote!(#serde_with_crate_path::Same); - let type_borrowcow = &syn::parse_quote!(BorrowCow); - if let Some(type_) = serde_as_options.r#as { - // If the field is not borrowed yet, check if we need to borrow it. - if serde_options.borrow.is_none() && has_type_embedded(&type_, type_borrowcow) { - let attr_borrow = parse_quote!(#[serde(borrow)]); - field.attrs.push(attr_borrow); - } + if let Some(type_) = &serde_as_options.r#as { + emit_borrow_annotation(&serde_options, type_, field); + emit_default_annotation(&serde_as_options, &serde_options, type_, field); - let replacement_type = replace_infer_type_with_type(type_, type_same); + let replacement_type = replace_infer_type_with_type(type_.clone(), type_same); let attr_inner_tokens = quote!(#serde_with_crate_path::As::<#replacement_type>).to_string(); let attr = parse_quote!(#[serde(with = #attr_inner_tokens)]); field.attrs.push(attr); } - if let Some(type_) = serde_as_options.deserialize_as { - // If the field is not borrowed yet, check if we need to borrow it. - if serde_options.borrow.is_none() && has_type_embedded(&type_, type_borrowcow) { - let attr_borrow = parse_quote!(#[serde(borrow)]); - field.attrs.push(attr_borrow); - } + if let Some(type_) = &serde_as_options.deserialize_as { + emit_borrow_annotation(&serde_options, type_, field); + emit_default_annotation(&serde_as_options, &serde_options, type_, field); - let replacement_type = replace_infer_type_with_type(type_, type_same); + let replacement_type = replace_infer_type_with_type(type_.clone(), type_same); let attr_inner_tokens = quote!(#serde_with_crate_path::As::<#replacement_type>::deserialize).to_string(); let attr = parse_quote!(#[serde(deserialize_with = #attr_inner_tokens)]);