From 22b69fc5c90107ce62e21a1f236cdb0d769f3b08 Mon Sep 17 00:00:00 2001 From: Erick Tryzelaar Date: Mon, 7 Sep 2015 13:02:53 -0700 Subject: [PATCH 1/5] docs(serde): Fix typo --- serde/src/ser/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/serde/src/ser/mod.rs b/serde/src/ser/mod.rs index b07e654a..7dc9b1de 100644 --- a/serde/src/ser/mod.rs +++ b/serde/src/ser/mod.rs @@ -337,7 +337,7 @@ pub trait SeqVisitor { } } -/// A trait that is used by a `Serializer` to iterate through a map. +/// A trait that is used by a `Serialize` to iterate through a map. pub trait MapVisitor { /// Serializes a map item in the serializer. /// From 76cca814f07b6abd9b1793424df02bab7b6a098f Mon Sep 17 00:00:00 2001 From: Erick Tryzelaar Date: Mon, 7 Sep 2015 13:10:23 -0700 Subject: [PATCH 2/5] docs(readme): Improve the readme --- README.md | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8010784e..6c4f07fc 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,8 @@ Documentation is available at: * [serde\_json](https://serde-rs.github.io/serde/serde_json/serde_json/index.html) * [serde\_codegen](https://serde-rs.github.io/serde/serde_codegen/serde_codegen/index.html) -Using Serde -=========== +Using Serde with Nightly Rust and serde\_macros +=============================================== Here is a simple example that demonstrates how to use Serde by serializing and deserializing to JSON. Serde comes with some powerful code generation libraries @@ -76,6 +76,9 @@ When run, it produces: Point { x: 1, y: 2 } ``` +Using Serde with Stable Rust, syntex, and serde\_codegen +======================================================== + Stable Rust is a little more complicated because it does not yet support compiler plugins. Instead we need to use the code generation library [syntex](https://github.com/erickt/rust-syntex) for this: @@ -215,6 +218,19 @@ include!(concat!(env!("OUT_DIR"), "/main.rs")); The `src/main.rs.in` is the same as before. +Then to run with stable: + +``` +% cargo build +... +``` + +Or with nightly: + +```rust +% cargo build --features nightly --no-default-features +... + Serialization without Macros ============================ @@ -311,6 +327,8 @@ as a named map. Its visitor uses a simple state machine to iterate through all the fields: ```rust +extern crate serde; + struct Point { x: i32, y: i32, @@ -479,6 +497,13 @@ deserializes an enum variant from a string. So for our `Point` example from before, we need to generate: ```rust +extern crate serde; + +struct Point { + x: i32, + y: i32, +} + enum PointField { X, Y, @@ -507,11 +532,7 @@ impl serde::Deserialize for PointField { deserializer.visit(PointFieldVisitor) } } -``` -This is then used in our actual deserializer: - -```rust impl serde::Deserialize for Point { fn deserialize(deserializer: &mut D) -> Result where D: serde::de::Deserializer From c68ab508c03a3c0c36819a9a90a5782a833055ad Mon Sep 17 00:00:00 2001 From: Erick Tryzelaar Date: Mon, 7 Sep 2015 13:13:32 -0700 Subject: [PATCH 3/5] refactor(codegen): Simplify parsing attributes --- serde_codegen/src/attr.rs | 175 ++++++++++++++++++++++++++++--------- serde_codegen/src/de.rs | 12 +-- serde_codegen/src/field.rs | 140 ++--------------------------- 3 files changed, 145 insertions(+), 182 deletions(-) diff --git a/serde_codegen/src/attr.rs b/serde_codegen/src/attr.rs index e4015546..51a83f73 100644 --- a/serde_codegen/src/attr.rs +++ b/serde_codegen/src/attr.rs @@ -5,7 +5,10 @@ use syntax::ast; use syntax::ext::base::ExtCtxt; use syntax::ptr::P; +use aster; + /// Represents field name information +#[derive(Debug)] pub enum FieldNames { Global(P), Format{ @@ -15,6 +18,7 @@ pub enum FieldNames { } /// Represents field attribute information +#[derive(Debug)] pub struct FieldAttrs { skip_serializing_field: bool, names: FieldNames, @@ -22,36 +26,6 @@ pub struct FieldAttrs { } impl FieldAttrs { - /// Create a FieldAttr with a single default field name - pub fn new( - skip_serializing_field: bool, - default_value: bool, - name: P, - ) -> FieldAttrs { - FieldAttrs { - skip_serializing_field: skip_serializing_field, - names: FieldNames::Global(name), - use_default: default_value, - } - } - - /// Create a FieldAttr with format specific field names - pub fn new_with_formats( - skip_serializing_field: bool, - default_value: bool, - default_name: P, - formats: HashMap, P>, - ) -> FieldAttrs { - FieldAttrs { - skip_serializing_field: skip_serializing_field, - names: FieldNames::Format { - formats: formats, - default: default_name, - }, - use_default: default_value, - } - } - /// Return a set of formats that the field has attributes for. pub fn formats(&self) -> HashSet> { match self.names { @@ -72,20 +46,19 @@ impl FieldAttrs { /// that implements `Serializer`. pub fn serializer_key_expr(self, cx: &ExtCtxt) -> P { match self.names { - FieldNames::Global(x) => x, - FieldNames::Format{formats, default} => { + FieldNames::Global(name) => name, + FieldNames::Format { formats, default } => { let arms = formats.iter() .map(|(fmt, lit)| { quote_arm!(cx, $fmt => { $lit }) }) .collect::>(); quote_expr!(cx, - { - match S::format() { - $arms - _ => { $default } - } - }) + match S::format() { + $arms + _ => { $default } + } + ) }, } } @@ -94,17 +67,17 @@ impl FieldAttrs { pub fn default_key_expr(&self) -> &P { match self.names { FieldNames::Global(ref expr) => expr, - FieldNames::Format{formats: _, ref default} => default + FieldNames::Format{formats: _, ref default} => default, } } /// Return the field name for the field in the specified format. pub fn key_expr(&self, format: &P) -> &P { match self.names { - FieldNames::Global(ref expr) => - expr, - FieldNames::Format{ref formats, ref default} => + FieldNames::Global(ref expr) => expr, + FieldNames::Format { ref formats, ref default } => { formats.get(format).unwrap_or(default) + } } } @@ -118,3 +91,121 @@ impl FieldAttrs { self.skip_serializing_field } } + +pub struct FieldAttrsBuilder<'a> { + builder: &'a aster::AstBuilder, + skip_serializing_field: bool, + name: Option>, + format_rename: HashMap, P>, + use_default: bool, +} + +impl<'a> FieldAttrsBuilder<'a> { + pub fn new(builder: &'a aster::AstBuilder) -> FieldAttrsBuilder<'a> { + FieldAttrsBuilder { + builder: builder, + skip_serializing_field: false, + name: None, + format_rename: HashMap::new(), + use_default: false, + } + } + + pub fn field(mut self, field: &ast::StructField) -> FieldAttrsBuilder<'a> { + match field.node.kind { + ast::NamedField(name, _) => { + self.name = Some(self.builder.expr().str(name)); + } + ast::UnnamedField(_) => { } + }; + + self.attrs(&field.node.attrs) + } + + pub fn attrs(self, attrs: &[ast::Attribute]) -> FieldAttrsBuilder<'a> { + attrs.iter().fold(self, FieldAttrsBuilder::attr) + } + + pub fn attr(self, attr: &ast::Attribute) -> FieldAttrsBuilder<'a> { + match attr.node.value.node { + ast::MetaList(ref name, ref items) if name == &"serde" => { + items.iter().fold(self, FieldAttrsBuilder::meta_item) + } + _ => { + self + } + } + } + + pub fn meta_item(mut self, meta_item: &P) -> FieldAttrsBuilder<'a> { + match meta_item.node { + ast::MetaNameValue(ref name, ref lit) if name == &"rename" => { + let expr = self.builder.expr().build_lit(P(lit.clone())); + + self.name(expr) + } + ast::MetaList(ref name, ref items) if name == &"rename" => { + for item in items { + match item.node { + ast::MetaNameValue(ref name, ref lit) => { + let name = self.builder.expr().str(name); + let expr = self.builder.expr().build_lit(P(lit.clone())); + + self = self.format_rename(name, expr); + } + _ => { } + } + } + self + } + ast::MetaWord(ref name) if name == &"default" => { + self.default() + } + ast::MetaWord(ref name) if name == &"skip_serializing" => { + self.skip_serializing_field() + } + _ => { + // Ignore unknown meta variables for now. + self + } + } + } + + pub fn skip_serializing_field(mut self) -> FieldAttrsBuilder<'a> { + self.skip_serializing_field = true; + self + } + + pub fn name(mut self, name: P) -> FieldAttrsBuilder<'a> { + self.name = Some(name); + self + } + + pub fn format_rename(mut self, format: P, name: P) -> FieldAttrsBuilder<'a> { + self.format_rename.insert(format, name); + self + } + + pub fn default(mut self) -> FieldAttrsBuilder<'a> { + self.use_default = true; + self + } + + pub fn build(self) -> FieldAttrs { + let name = self.name.expect("here"); + let names = if self.format_rename.is_empty() { + FieldNames::Global(name) + } else { + FieldNames::Format { + formats: self.format_rename, + default: name, + } + }; + + FieldAttrs { + skip_serializing_field: self.skip_serializing_field, + names: names, + use_default: self.use_default, + } + } +} diff --git a/serde_codegen/src/de.rs b/serde_codegen/src/de.rs index c903e706..3f343ed3 100644 --- a/serde_codegen/src/de.rs +++ b/serde_codegen/src/de.rs @@ -543,11 +543,13 @@ fn deserialize_item_enum( cx, builder, enum_def.variants.iter() - .map(|variant| - attr::FieldAttrs::new( - false, - true, - builder.expr().str(variant.node.name))) + .map(|variant| { + let expr = builder.expr().str(variant.node.name); + attr::FieldAttrsBuilder::new(builder) + .name(expr) + .default() + .build() + }) .collect() ); diff --git a/serde_codegen/src/field.rs b/serde_codegen/src/field.rs index 6f10eacf..6447b9b2 100644 --- a/serde_codegen/src/field.rs +++ b/serde_codegen/src/field.rs @@ -1,147 +1,17 @@ -use std::collections::HashMap; +use syntax::ast; +use syntax::ext::base::ExtCtxt; use aster; - -use syntax::ast; -use syntax::attr; -use syntax::ext::base::ExtCtxt; -use syntax::ptr::P; - -use attr::FieldAttrs; - -enum Rename<'a> { - None, - Global(&'a ast::Lit), - Format(HashMap, &'a ast::Lit>) -} - -fn rename<'a>( - builder: &aster::AstBuilder, - mi: &'a ast::MetaItem, - ) -> Option> -{ - match mi.node { - ast::MetaNameValue(ref n, ref lit) => { - if n == &"rename" { - Some(Rename::Global(lit)) - } else { - None - } - }, - ast::MetaList(ref n, ref items) => { - if n == &"rename" { - let mut m = HashMap::new(); - m.extend( - items.iter() - .filter_map( - |item| - match item.node { - ast::MetaNameValue(ref n, ref lit) => - Some((builder.expr().str(n), - lit)), - _ => None - })); - Some(Rename::Format(m)) - } else { - None - } - }, - _ => None - } -} - -fn default_value(mi: &ast::MetaItem) -> bool { - if let ast::MetaItem_::MetaWord(ref n) = mi.node { - n == &"default" - } else { - false - } -} - -fn skip_serializing_field(mi: &ast::MetaItem) -> bool { - if let ast::MetaItem_::MetaWord(ref n) = mi.node { - n == &"skip_serializing" - } else { - false - } -} - -fn field_attrs<'a>( - builder: &aster::AstBuilder, - field: &'a ast::StructField, -) -> (Rename<'a>, bool, bool) { - field.node.attrs.iter() - .find(|sa| { - if let ast::MetaList(ref n, _) = sa.node.value.node { - n == &"serde" - } else { - false - } - }) - .and_then(|sa| { - if let ast::MetaList(_, ref vals) = sa.node.value.node { - attr::mark_used(&sa); - Some(( - vals.iter() - .fold(None, |v, mi| v.or(rename(builder, mi))) - .unwrap_or(Rename::None), - vals.iter().any(|mi| default_value(mi)), - vals.iter().any(|mi| skip_serializing_field(mi)), - )) - } else { - Some((Rename::None, false, false)) - } - }) - .unwrap_or((Rename::None, false, false)) -} +use attr::{FieldAttrs, FieldAttrsBuilder}; pub fn struct_field_attrs( - cx: &ExtCtxt, + _cx: &ExtCtxt, builder: &aster::AstBuilder, struct_def: &ast::StructDef, ) -> Vec { struct_def.fields.iter() .map(|field| { - match field_attrs(builder, field) { - (Rename::Global(rename), default_value, skip_serializing_field) => - FieldAttrs::new( - skip_serializing_field, - default_value, - builder.expr().build_lit(P(rename.clone()))), - (Rename::Format(renames), default_value, skip_serializing_field) => { - let mut res = HashMap::new(); - res.extend( - renames.into_iter() - .map(|(k,v)| - (k, builder.expr().build_lit(P(v.clone()))))); - FieldAttrs::new_with_formats( - skip_serializing_field, - default_value, - default_field_name(cx, builder, field.node.kind), - res) - }, - (Rename::None, default_value, skip_serializing_field) => { - FieldAttrs::new( - skip_serializing_field, - default_value, - default_field_name(cx, builder, field.node.kind)) - } - } + FieldAttrsBuilder::new(builder).field(field).build() }) .collect() } - -fn default_field_name( - cx: &ExtCtxt, - builder: &aster::AstBuilder, - kind: ast::StructFieldKind, -) -> P { - match kind { - ast::NamedField(name, _) => { - builder.expr().str(name) - } - ast::UnnamedField(_) => { - cx.bug("struct has named and unnamed fields") - } - } -} From c4392ff256c1eea6595daea987c44c084f299c11 Mon Sep 17 00:00:00 2001 From: Erick Tryzelaar Date: Mon, 7 Sep 2015 16:44:56 -0700 Subject: [PATCH 4/5] feat(codegen): Add more attributes to skip serializing These attributes are `#[serde(skip_serializing_if_none)]` and `#[serde(skip_serializing_if_empty)]`. --- serde_codegen/src/attr.rs | 40 +++++- serde_codegen/src/ser.rs | 51 ++++++-- serde_tests/tests/test_annotations.rs | 172 ++++++++++++++++++++++++++ 3 files changed, 247 insertions(+), 16 deletions(-) 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, + ] + ); +} From 72af0896e89dcd92879c8aa3ead8aa81e4d23e71 Mon Sep 17 00:00:00 2001 From: Erick Tryzelaar Date: Mon, 7 Sep 2015 16:54:27 -0700 Subject: [PATCH 5/5] docs(codegen): Document annotations --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index 6c4f07fc..09ca8f36 100644 --- a/README.md +++ b/README.md @@ -578,6 +578,21 @@ impl serde::de::Visitor for PointVisitor { } ``` +Annotations +=========== + +`serde_codegen` and `serde_macros` support annotations that help to customize +how types are serialized. Here are the supported annotations: + +| Annotation | Function | +| ---------- | -------- | +| `#[serde(rename(json="name1", xml="name2"))` | Serialize this field with the given name for the given formats | +| `#[serde(default)` | If the value is not specified, use the `Default::default()` | +| `#[serde(rename="name")` | Serialize this field with the given name | +| `#[serde(skip_serializing)` | Do not serialize this value | +| `#[serde(skip_serializing_if_empty)` | Do not serialize this value if `$value.is_empty()` is `true` | +| `#[serde(skip_serializing_if_none)` | Do not serialize this value if `$value.is_none()` is `true` | + Serialization Formats Using Serde =================================