Merge pull request #155 from erickt/skip

Add skip serializing fields if empty or none
This commit is contained in:
Erick Tryzelaar 2015-10-05 14:51:46 -07:00
commit 00cd2900e7
7 changed files with 433 additions and 203 deletions

View File

@ -17,8 +17,8 @@ Documentation is available at:
* [serde\_json](https://serde-rs.github.io/serde/serde_json/serde_json/index.html) * [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) * [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 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 deserializing to JSON. Serde comes with some powerful code generation libraries
@ -76,6 +76,9 @@ When run, it produces:
Point { x: 1, y: 2 } 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 Stable Rust is a little more complicated because it does not yet support
compiler plugins. Instead we need to use the code generation library compiler plugins. Instead we need to use the code generation library
[syntex](https://github.com/erickt/rust-syntex) for this: [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. 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 Serialization without Macros
============================ ============================
@ -311,6 +327,8 @@ as a named map. Its visitor uses a simple state machine to iterate through all
the fields: the fields:
```rust ```rust
extern crate serde;
struct Point { struct Point {
x: i32, x: i32,
y: 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: before, we need to generate:
```rust ```rust
extern crate serde;
struct Point {
x: i32,
y: i32,
}
enum PointField { enum PointField {
X, X,
Y, Y,
@ -507,11 +532,7 @@ impl serde::Deserialize for PointField {
deserializer.visit(PointFieldVisitor) deserializer.visit(PointFieldVisitor)
} }
} }
```
This is then used in our actual deserializer:
```rust
impl serde::Deserialize for Point { impl serde::Deserialize for Point {
fn deserialize<D>(deserializer: &mut D) -> Result<Point, D::Error> fn deserialize<D>(deserializer: &mut D) -> Result<Point, D::Error>
where D: serde::de::Deserializer where D: serde::de::Deserializer
@ -557,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 Serialization Formats Using Serde
================================= =================================

View File

@ -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 { pub trait MapVisitor {
/// Serializes a map item in the serializer. /// Serializes a map item in the serializer.
/// ///

View File

@ -2,10 +2,14 @@ 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;
use aster;
/// Represents field name information /// Represents field name information
#[derive(Debug)]
pub enum FieldNames { pub enum FieldNames {
Global(P<ast::Expr>), Global(P<ast::Expr>),
Format{ Format{
@ -15,43 +19,16 @@ pub enum FieldNames {
} }
/// Represents field attribute information /// Represents field attribute information
#[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,
} }
impl FieldAttrs { impl FieldAttrs {
/// Create a FieldAttr with a single default field name
pub fn new(
skip_serializing_field: bool,
default_value: bool,
name: P<ast::Expr>,
) -> 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<ast::Expr>,
formats: HashMap<P<ast::Expr>, P<ast::Expr>>,
) -> 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. /// Return a set of formats that the field has attributes for.
pub fn formats(&self) -> HashSet<P<ast::Expr>> { pub fn formats(&self) -> HashSet<P<ast::Expr>> {
match self.names { match self.names {
@ -70,22 +47,21 @@ 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(x) => x, 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 })
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
quote_expr!(cx, quote_expr!(cx,
{ match S::format() {
match S::format() { $arms
$arms _ => { $default }
_ => { $default } }
} )
})
}, },
} }
} }
@ -94,17 +70,17 @@ impl FieldAttrs {
pub fn default_key_expr(&self) -> &P<ast::Expr> { pub fn default_key_expr(&self) -> &P<ast::Expr> {
match self.names { match self.names {
FieldNames::Global(ref expr) => expr, 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. /// Return the field name for the field in the specified format.
pub fn key_expr(&self, format: &P<ast::Expr>) -> &P<ast::Expr> { pub fn key_expr(&self, format: &P<ast::Expr>) -> &P<ast::Expr> {
match self.names { match self.names {
FieldNames::Global(ref expr) => FieldNames::Global(ref expr) => expr,
expr, FieldNames::Format { ref formats, ref default } => {
FieldNames::Format{ref formats, ref default} =>
formats.get(format).unwrap_or(default) formats.get(format).unwrap_or(default)
}
} }
} }
@ -117,4 +93,153 @@ 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> {
builder: &'a aster::AstBuilder,
skip_serializing_field: bool,
skip_serializing_field_if_empty: bool,
skip_serializing_field_if_none: bool,
name: Option<P<ast::Expr>>,
format_rename: HashMap<P<ast::Expr>, P<ast::Expr>>,
use_default: bool,
}
impl<'a> FieldAttrsBuilder<'a> {
pub fn new(builder: &'a aster::AstBuilder) -> 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,
}
}
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" => {
attr::mark_used(&attr);
items.iter().fold(self, FieldAttrsBuilder::meta_item)
}
_ => {
self
}
}
}
pub fn meta_item(mut self, meta_item: &P<ast::MetaItem>) -> 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()
}
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
}
}
}
pub fn skip_serializing_field(mut self) -> FieldAttrsBuilder<'a> {
self.skip_serializing_field = true;
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> {
self.name = Some(name);
self
}
pub fn format_rename(mut self, format: P<ast::Expr>, name: P<ast::Expr>) -> 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,
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,
}
}
} }

View File

@ -543,11 +543,13 @@ fn deserialize_item_enum(
cx, cx,
builder, builder,
enum_def.variants.iter() enum_def.variants.iter()
.map(|variant| .map(|variant| {
attr::FieldAttrs::new( let expr = builder.expr().str(variant.node.name);
false, attr::FieldAttrsBuilder::new(builder)
true, .name(expr)
builder.expr().str(variant.node.name))) .default()
.build()
})
.collect() .collect()
); );

View File

@ -1,147 +1,17 @@
use std::collections::HashMap; use syntax::ast;
use syntax::ext::base::ExtCtxt;
use aster; use aster;
use attr::{FieldAttrs, FieldAttrsBuilder};
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<P<ast::Expr>, &'a ast::Lit>)
}
fn rename<'a>(
builder: &aster::AstBuilder,
mi: &'a ast::MetaItem,
) -> Option<Rename<'a>>
{
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))
}
pub fn struct_field_attrs( pub fn struct_field_attrs(
cx: &ExtCtxt, _cx: &ExtCtxt,
builder: &aster::AstBuilder, builder: &aster::AstBuilder,
struct_def: &ast::StructDef, struct_def: &ast::StructDef,
) -> Vec<FieldAttrs> { ) -> Vec<FieldAttrs> {
struct_def.fields.iter() struct_def.fields.iter()
.map(|field| { .map(|field| {
match field_attrs(builder, field) { FieldAttrsBuilder::new(builder).field(field).build()
(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))
}
}
}) })
.collect() .collect()
} }
fn default_field_name(
cx: &ExtCtxt,
builder: &aster::AstBuilder,
kind: ast::StructFieldKind,
) -> P<ast::Expr> {
match kind {
ast::NamedField(name, _) => {
builder.expr().str(name)
}
ast::UnnamedField(_) => {
cx.bug("struct has named and unnamed fields")
}
}
}

View File

@ -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); }
}
} }
} }

View File

@ -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,
]
);
}