diff --git a/serde_codegen/src/attr.rs b/serde_codegen/src/attr.rs index 51a83f73..af65f6d1 100644 --- a/serde_codegen/src/attr.rs +++ b/serde_codegen/src/attr.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use std::collections::HashSet; use syntax::ast; +use syntax::attr; use syntax::ext::base::ExtCtxt; use syntax::ptr::P; @@ -21,6 +22,8 @@ pub enum FieldNames { #[derive(Debug)] pub struct FieldAttrs { skip_serializing_field: bool, + skip_serializing_field_if_empty: bool, + skip_serializing_field_if_none: bool, names: FieldNames, use_default: bool, } @@ -44,10 +47,10 @@ impl FieldAttrs { /// /// The resulting expression assumes that `S` refers to a type /// that implements `Serializer`. - pub fn serializer_key_expr(self, cx: &ExtCtxt) -> P { + pub fn serializer_key_expr(&self, cx: &ExtCtxt) -> P { match self.names { - FieldNames::Global(name) => name, - FieldNames::Format { formats, default } => { + FieldNames::Global(ref name) => name.clone(), + FieldNames::Format { ref formats, ref default } => { let arms = formats.iter() .map(|(fmt, lit)| { quote_arm!(cx, $fmt => { $lit }) @@ -90,11 +93,21 @@ impl FieldAttrs { pub fn skip_serializing_field(&self) -> bool { self.skip_serializing_field } + + pub fn skip_serializing_field_if_empty(&self) -> bool { + self.skip_serializing_field_if_empty + } + + pub fn skip_serializing_field_if_none(&self) -> bool { + self.skip_serializing_field_if_none + } } pub struct FieldAttrsBuilder<'a> { builder: &'a aster::AstBuilder, skip_serializing_field: bool, + skip_serializing_field_if_empty: bool, + skip_serializing_field_if_none: bool, name: Option>, format_rename: HashMap, P>, use_default: bool, @@ -105,6 +118,8 @@ impl<'a> FieldAttrsBuilder<'a> { FieldAttrsBuilder { builder: builder, skip_serializing_field: false, + skip_serializing_field_if_empty: false, + skip_serializing_field_if_none: false, name: None, format_rename: HashMap::new(), use_default: false, @@ -129,6 +144,7 @@ impl<'a> FieldAttrsBuilder<'a> { pub fn attr(self, attr: &ast::Attribute) -> FieldAttrsBuilder<'a> { match attr.node.value.node { ast::MetaList(ref name, ref items) if name == &"serde" => { + attr::mark_used(&attr); items.iter().fold(self, FieldAttrsBuilder::meta_item) } _ => { @@ -164,6 +180,12 @@ impl<'a> FieldAttrsBuilder<'a> { ast::MetaWord(ref name) if name == &"skip_serializing" => { self.skip_serializing_field() } + ast::MetaWord(ref name) if name == &"skip_serializing_if_empty" => { + self.skip_serializing_field_if_empty() + } + ast::MetaWord(ref name) if name == &"skip_serializing_if_none" => { + self.skip_serializing_field_if_none() + } _ => { // Ignore unknown meta variables for now. self @@ -176,6 +198,16 @@ impl<'a> FieldAttrsBuilder<'a> { self } + pub fn skip_serializing_field_if_empty(mut self) -> FieldAttrsBuilder<'a> { + self.skip_serializing_field_if_empty = true; + self + } + + pub fn skip_serializing_field_if_none(mut self) -> FieldAttrsBuilder<'a> { + self.skip_serializing_field_if_none = true; + self + } + pub fn name(mut self, name: P) -> FieldAttrsBuilder<'a> { self.name = Some(name); self @@ -204,6 +236,8 @@ impl<'a> FieldAttrsBuilder<'a> { FieldAttrs { skip_serializing_field: self.skip_serializing_field, + skip_serializing_field_if_empty: self.skip_serializing_field_if_empty, + skip_serializing_field_if_none: self.skip_serializing_field_if_none, names: names, use_default: self.use_default, } diff --git a/serde_codegen/src/ser.rs b/serde_codegen/src/ser.rs index a749926f..6e9c33cd 100644 --- a/serde_codegen/src/ser.rs +++ b/serde_codegen/src/ser.rs @@ -580,23 +580,31 @@ fn serialize_struct_visitor( ) -> (P, P) where I: Iterator>, { + let value_exprs = value_exprs.collect::>(); + let field_attrs = struct_field_attrs(cx, builder, struct_def); - let len = struct_def.fields.len() - field_attrs.iter() - .fold(0, |sum, field| { - sum + if field.skip_serializing_field() { 1 } else { 0 } - }); - - let arms: Vec = field_attrs.into_iter() - .zip(value_exprs) + let arms: Vec = field_attrs.iter() + .zip(value_exprs.iter()) .filter(|&(ref field, _)| !field.skip_serializing_field()) .enumerate() - .map(|(i, (field, value_expr))| { + .map(|(i, (ref field, value_expr))| { let key_expr = field.serializer_key_expr(cx); + + let stmt = if field.skip_serializing_field_if_empty() { + quote_stmt!(cx, if $value_expr.is_empty() { continue; }) + } else if field.skip_serializing_field_if_none() { + quote_stmt!(cx, if $value_expr.is_none() { continue; }) + } else { + quote_stmt!(cx, {}) + }; + quote_arm!(cx, $i => { self.state += 1; - Ok( + $stmt + + return Ok( Some( try!( serializer.visit_struct_elt( @@ -605,7 +613,7 @@ fn serialize_struct_visitor( ) ) ) - ) + ); } ) }) @@ -622,6 +630,21 @@ fn serialize_struct_visitor( .strip_bounds() .build(); + let len = field_attrs.iter() + .zip(value_exprs.iter()) + .map(|(field, value_expr)| { + if field.skip_serializing_field() { + quote_expr!(cx, 0) + } else if field.skip_serializing_field_if_empty() { + quote_expr!(cx, if $value_expr.is_empty() { 0 } else { 1 }) + } else if field.skip_serializing_field_if_none() { + quote_expr!(cx, if $value_expr.is_none() { 0 } else { 1 }) + } else { + quote_expr!(cx, 1) + } + }) + .fold(quote_expr!(cx, 0), |sum, expr| quote_expr!(cx, $sum + $expr)); + ( quote_item!(cx, struct Visitor $visitor_impl_generics $where_clause { @@ -640,9 +663,11 @@ fn serialize_struct_visitor( fn visit(&mut self, serializer: &mut S) -> ::std::result::Result, S::Error> where S: ::serde::ser::Serializer, { - match self.state { - $arms - _ => Ok(None) + loop { + match self.state { + $arms + _ => { return Ok(None); } + } } } diff --git a/serde_tests/tests/test_annotations.rs b/serde_tests/tests/test_annotations.rs index 65508658..87e87960 100644 --- a/serde_tests/tests/test_annotations.rs +++ b/serde_tests/tests/test_annotations.rs @@ -39,6 +39,20 @@ struct SkipSerializingFields { b: A, } +#[derive(Debug, PartialEq, Deserialize, Serialize)] +struct SkipSerializingIfEmptyFields { + a: i8, + #[serde(skip_serializing_if_empty, default)] + b: Vec, +} + +#[derive(Debug, PartialEq, Deserialize, Serialize)] +struct SkipSerializingIfNoneFields { + a: i8, + #[serde(skip_serializing_if_none, default)] + b: Option, +} + #[test] fn test_default() { assert_de_tokens( @@ -169,3 +183,161 @@ fn test_skip_serializing_fields() { ] ); } + +#[test] +fn test_skip_serializing_fields_if_empty() { + assert_ser_tokens( + &SkipSerializingIfEmptyFields:: { + a: 1, + b: vec![], + }, + &[ + Token::StructStart("SkipSerializingIfEmptyFields", Some(1)), + + Token::MapSep, + Token::Str("a"), + Token::I8(1), + + Token::MapEnd, + ] + ); + + assert_de_tokens( + &SkipSerializingIfEmptyFields:: { + a: 1, + b: vec![], + }, + vec![ + Token::StructStart("SkipSerializingIfEmptyFields", Some(1)), + + Token::MapSep, + Token::Str("a"), + Token::I8(1), + + Token::MapEnd, + ] + ); + + assert_ser_tokens( + &SkipSerializingIfEmptyFields { + a: 1, + b: vec![2], + }, + &[ + Token::StructStart("SkipSerializingIfEmptyFields", Some(2)), + + Token::MapSep, + Token::Str("a"), + Token::I8(1), + + Token::MapSep, + Token::Str("b"), + Token::SeqStart(Some(1)), + Token::SeqSep, + Token::I32(2), + Token::SeqEnd, + + Token::MapEnd, + ] + ); + + assert_de_tokens( + &SkipSerializingIfEmptyFields { + a: 1, + b: vec![2], + }, + vec![ + Token::StructStart("SkipSerializingIfEmptyFields", Some(2)), + + Token::MapSep, + Token::Str("a"), + Token::I8(1), + + Token::MapSep, + Token::Str("b"), + Token::SeqStart(Some(1)), + Token::SeqSep, + Token::I32(2), + Token::SeqEnd, + + Token::MapEnd, + ] + ); +} + +#[test] +fn test_skip_serializing_fields_if_none() { + assert_ser_tokens( + &SkipSerializingIfNoneFields:: { + a: 1, + b: None, + }, + &[ + Token::StructStart("SkipSerializingIfNoneFields", Some(1)), + + Token::MapSep, + Token::Str("a"), + Token::I8(1), + + Token::MapEnd, + ] + ); + + assert_de_tokens( + &SkipSerializingIfNoneFields:: { + a: 1, + b: None, + }, + vec![ + Token::StructStart("SkipSerializingIfNoneFields", Some(1)), + + Token::MapSep, + Token::Str("a"), + Token::I8(1), + + Token::MapEnd, + ] + ); + + assert_ser_tokens( + &SkipSerializingIfNoneFields { + a: 1, + b: Some(2), + }, + &[ + Token::StructStart("SkipSerializingIfNoneFields", Some(2)), + + Token::MapSep, + Token::Str("a"), + Token::I8(1), + + Token::MapSep, + Token::Str("b"), + Token::Option(true), + Token::I32(2), + + Token::MapEnd, + ] + ); + + assert_de_tokens( + &SkipSerializingIfNoneFields { + a: 1, + b: Some(2), + }, + vec![ + Token::StructStart("SkipSerializingIfNoneFields", Some(2)), + + Token::MapSep, + Token::Str("a"), + Token::I8(1), + + Token::MapSep, + Token::Str("b"), + Token::Option(true), + Token::I32(2), + + Token::MapEnd, + ] + ); +}