mirror of
https://github.com/pezkuwichain/serde.git
synced 2026-06-15 15:31:01 +00:00
feat(codegen): Add more attributes to skip serializing
These attributes are `#[serde(skip_serializing_if_none)]` and `#[serde(skip_serializing_if_empty)]`.
This commit is contained in:
@@ -2,6 +2,7 @@ use std::collections::HashMap;
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use syntax::ast;
|
use syntax::ast;
|
||||||
|
use syntax::attr;
|
||||||
use syntax::ext::base::ExtCtxt;
|
use syntax::ext::base::ExtCtxt;
|
||||||
use syntax::ptr::P;
|
use syntax::ptr::P;
|
||||||
|
|
||||||
@@ -21,6 +22,8 @@ pub enum FieldNames {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct FieldAttrs {
|
pub struct FieldAttrs {
|
||||||
skip_serializing_field: bool,
|
skip_serializing_field: bool,
|
||||||
|
skip_serializing_field_if_empty: bool,
|
||||||
|
skip_serializing_field_if_none: bool,
|
||||||
names: FieldNames,
|
names: FieldNames,
|
||||||
use_default: bool,
|
use_default: bool,
|
||||||
}
|
}
|
||||||
@@ -44,10 +47,10 @@ impl FieldAttrs {
|
|||||||
///
|
///
|
||||||
/// The resulting expression assumes that `S` refers to a type
|
/// The resulting expression assumes that `S` refers to a type
|
||||||
/// that implements `Serializer`.
|
/// that implements `Serializer`.
|
||||||
pub fn serializer_key_expr(self, cx: &ExtCtxt) -> P<ast::Expr> {
|
pub fn serializer_key_expr(&self, cx: &ExtCtxt) -> P<ast::Expr> {
|
||||||
match self.names {
|
match self.names {
|
||||||
FieldNames::Global(name) => name,
|
FieldNames::Global(ref name) => name.clone(),
|
||||||
FieldNames::Format { formats, default } => {
|
FieldNames::Format { ref formats, ref default } => {
|
||||||
let arms = formats.iter()
|
let arms = formats.iter()
|
||||||
.map(|(fmt, lit)| {
|
.map(|(fmt, lit)| {
|
||||||
quote_arm!(cx, $fmt => { $lit })
|
quote_arm!(cx, $fmt => { $lit })
|
||||||
@@ -90,11 +93,21 @@ impl FieldAttrs {
|
|||||||
pub fn skip_serializing_field(&self) -> bool {
|
pub fn skip_serializing_field(&self) -> bool {
|
||||||
self.skip_serializing_field
|
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> {
|
pub struct FieldAttrsBuilder<'a> {
|
||||||
builder: &'a aster::AstBuilder,
|
builder: &'a aster::AstBuilder,
|
||||||
skip_serializing_field: bool,
|
skip_serializing_field: bool,
|
||||||
|
skip_serializing_field_if_empty: bool,
|
||||||
|
skip_serializing_field_if_none: bool,
|
||||||
name: Option<P<ast::Expr>>,
|
name: Option<P<ast::Expr>>,
|
||||||
format_rename: HashMap<P<ast::Expr>, P<ast::Expr>>,
|
format_rename: HashMap<P<ast::Expr>, P<ast::Expr>>,
|
||||||
use_default: bool,
|
use_default: bool,
|
||||||
@@ -105,6 +118,8 @@ impl<'a> FieldAttrsBuilder<'a> {
|
|||||||
FieldAttrsBuilder {
|
FieldAttrsBuilder {
|
||||||
builder: builder,
|
builder: builder,
|
||||||
skip_serializing_field: false,
|
skip_serializing_field: false,
|
||||||
|
skip_serializing_field_if_empty: false,
|
||||||
|
skip_serializing_field_if_none: false,
|
||||||
name: None,
|
name: None,
|
||||||
format_rename: HashMap::new(),
|
format_rename: HashMap::new(),
|
||||||
use_default: false,
|
use_default: false,
|
||||||
@@ -129,6 +144,7 @@ impl<'a> FieldAttrsBuilder<'a> {
|
|||||||
pub fn attr(self, attr: &ast::Attribute) -> FieldAttrsBuilder<'a> {
|
pub fn attr(self, attr: &ast::Attribute) -> FieldAttrsBuilder<'a> {
|
||||||
match attr.node.value.node {
|
match attr.node.value.node {
|
||||||
ast::MetaList(ref name, ref items) if name == &"serde" => {
|
ast::MetaList(ref name, ref items) if name == &"serde" => {
|
||||||
|
attr::mark_used(&attr);
|
||||||
items.iter().fold(self, FieldAttrsBuilder::meta_item)
|
items.iter().fold(self, FieldAttrsBuilder::meta_item)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
@@ -164,6 +180,12 @@ impl<'a> FieldAttrsBuilder<'a> {
|
|||||||
ast::MetaWord(ref name) if name == &"skip_serializing" => {
|
ast::MetaWord(ref name) if name == &"skip_serializing" => {
|
||||||
self.skip_serializing_field()
|
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.
|
// Ignore unknown meta variables for now.
|
||||||
self
|
self
|
||||||
@@ -176,6 +198,16 @@ impl<'a> FieldAttrsBuilder<'a> {
|
|||||||
self
|
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<ast::Expr>) -> FieldAttrsBuilder<'a> {
|
pub fn name(mut self, name: P<ast::Expr>) -> FieldAttrsBuilder<'a> {
|
||||||
self.name = Some(name);
|
self.name = Some(name);
|
||||||
self
|
self
|
||||||
@@ -204,6 +236,8 @@ impl<'a> FieldAttrsBuilder<'a> {
|
|||||||
|
|
||||||
FieldAttrs {
|
FieldAttrs {
|
||||||
skip_serializing_field: self.skip_serializing_field,
|
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,
|
names: names,
|
||||||
use_default: self.use_default,
|
use_default: self.use_default,
|
||||||
}
|
}
|
||||||
|
|||||||
+38
-13
@@ -580,23 +580,31 @@ fn serialize_struct_visitor<I>(
|
|||||||
) -> (P<ast::Item>, P<ast::Item>)
|
) -> (P<ast::Item>, P<ast::Item>)
|
||||||
where I: Iterator<Item=P<ast::Expr>>,
|
where I: Iterator<Item=P<ast::Expr>>,
|
||||||
{
|
{
|
||||||
|
let value_exprs = value_exprs.collect::<Vec<_>>();
|
||||||
|
|
||||||
let field_attrs = struct_field_attrs(cx, builder, struct_def);
|
let field_attrs = struct_field_attrs(cx, builder, struct_def);
|
||||||
|
|
||||||
let len = struct_def.fields.len() - field_attrs.iter()
|
let arms: Vec<ast::Arm> = field_attrs.iter()
|
||||||
.fold(0, |sum, field| {
|
.zip(value_exprs.iter())
|
||||||
sum + if field.skip_serializing_field() { 1 } else { 0 }
|
|
||||||
});
|
|
||||||
|
|
||||||
let arms: Vec<ast::Arm> = field_attrs.into_iter()
|
|
||||||
.zip(value_exprs)
|
|
||||||
.filter(|&(ref field, _)| !field.skip_serializing_field())
|
.filter(|&(ref field, _)| !field.skip_serializing_field())
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, (field, value_expr))| {
|
.map(|(i, (ref field, value_expr))| {
|
||||||
let key_expr = field.serializer_key_expr(cx);
|
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,
|
quote_arm!(cx,
|
||||||
$i => {
|
$i => {
|
||||||
self.state += 1;
|
self.state += 1;
|
||||||
Ok(
|
$stmt
|
||||||
|
|
||||||
|
return Ok(
|
||||||
Some(
|
Some(
|
||||||
try!(
|
try!(
|
||||||
serializer.visit_struct_elt(
|
serializer.visit_struct_elt(
|
||||||
@@ -605,7 +613,7 @@ fn serialize_struct_visitor<I>(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -622,6 +630,21 @@ fn serialize_struct_visitor<I>(
|
|||||||
.strip_bounds()
|
.strip_bounds()
|
||||||
.build();
|
.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,
|
quote_item!(cx,
|
||||||
struct Visitor $visitor_impl_generics $where_clause {
|
struct Visitor $visitor_impl_generics $where_clause {
|
||||||
@@ -640,9 +663,11 @@ fn serialize_struct_visitor<I>(
|
|||||||
fn visit<S>(&mut self, serializer: &mut S) -> ::std::result::Result<Option<()>, S::Error>
|
fn visit<S>(&mut self, serializer: &mut S) -> ::std::result::Result<Option<()>, S::Error>
|
||||||
where S: ::serde::ser::Serializer,
|
where S: ::serde::ser::Serializer,
|
||||||
{
|
{
|
||||||
match self.state {
|
loop {
|
||||||
$arms
|
match self.state {
|
||||||
_ => Ok(None)
|
$arms
|
||||||
|
_ => { return Ok(None); }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,20 @@ struct SkipSerializingFields<A: default::Default> {
|
|||||||
b: A,
|
b: A,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Deserialize, Serialize)]
|
||||||
|
struct SkipSerializingIfEmptyFields<A: default::Default> {
|
||||||
|
a: i8,
|
||||||
|
#[serde(skip_serializing_if_empty, default)]
|
||||||
|
b: Vec<A>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Deserialize, Serialize)]
|
||||||
|
struct SkipSerializingIfNoneFields<A: default::Default> {
|
||||||
|
a: i8,
|
||||||
|
#[serde(skip_serializing_if_none, default)]
|
||||||
|
b: Option<A>,
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_default() {
|
fn test_default() {
|
||||||
assert_de_tokens(
|
assert_de_tokens(
|
||||||
@@ -169,3 +183,161 @@ fn test_skip_serializing_fields() {
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_skip_serializing_fields_if_empty() {
|
||||||
|
assert_ser_tokens(
|
||||||
|
&SkipSerializingIfEmptyFields::<i32> {
|
||||||
|
a: 1,
|
||||||
|
b: vec![],
|
||||||
|
},
|
||||||
|
&[
|
||||||
|
Token::StructStart("SkipSerializingIfEmptyFields", Some(1)),
|
||||||
|
|
||||||
|
Token::MapSep,
|
||||||
|
Token::Str("a"),
|
||||||
|
Token::I8(1),
|
||||||
|
|
||||||
|
Token::MapEnd,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_de_tokens(
|
||||||
|
&SkipSerializingIfEmptyFields::<i32> {
|
||||||
|
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::<i32> {
|
||||||
|
a: 1,
|
||||||
|
b: None,
|
||||||
|
},
|
||||||
|
&[
|
||||||
|
Token::StructStart("SkipSerializingIfNoneFields", Some(1)),
|
||||||
|
|
||||||
|
Token::MapSep,
|
||||||
|
Token::Str("a"),
|
||||||
|
Token::I8(1),
|
||||||
|
|
||||||
|
Token::MapEnd,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_de_tokens(
|
||||||
|
&SkipSerializingIfNoneFields::<i32> {
|
||||||
|
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,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user