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\_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<D>(deserializer: &mut D) -> Result<Point, D::Error>
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
=================================

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

View File

@ -2,10 +2,14 @@ use std::collections::HashMap;
use std::collections::HashSet;
use syntax::ast;
use syntax::attr;
use syntax::ext::base::ExtCtxt;
use syntax::ptr::P;
use aster;
/// Represents field name information
#[derive(Debug)]
pub enum FieldNames {
Global(P<ast::Expr>),
Format{
@ -15,43 +19,16 @@ pub enum FieldNames {
}
/// Represents field attribute information
#[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,
}
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.
pub fn formats(&self) -> HashSet<P<ast::Expr>> {
match self.names {
@ -70,22 +47,21 @@ impl FieldAttrs {
///
/// The resulting expression assumes that `S` refers to a type
/// 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 {
FieldNames::Global(x) => x,
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 })
})
.collect::<Vec<_>>();
quote_expr!(cx,
{
match S::format() {
$arms
_ => { $default }
}
})
match S::format() {
$arms
_ => { $default }
}
)
},
}
}
@ -94,17 +70,17 @@ impl FieldAttrs {
pub fn default_key_expr(&self) -> &P<ast::Expr> {
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<ast::Expr>) -> &P<ast::Expr> {
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)
}
}
}
@ -117,4 +93,153 @@ 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<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,
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()
);

View File

@ -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<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))
}
use attr::{FieldAttrs, FieldAttrsBuilder};
pub fn struct_field_attrs(
cx: &ExtCtxt,
_cx: &ExtCtxt,
builder: &aster::AstBuilder,
struct_def: &ast::StructDef,
) -> Vec<FieldAttrs> {
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<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>)
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 len = struct_def.fields.len() - field_attrs.iter()
.fold(0, |sum, field| {
sum + if field.skip_serializing_field() { 1 } else { 0 }
});
let arms: Vec<ast::Arm> = field_attrs.into_iter()
.zip(value_exprs)
let arms: Vec<ast::Arm> = 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<I>(
)
)
)
)
);
}
)
})
@ -622,6 +630,21 @@ fn serialize_struct_visitor<I>(
.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<I>(
fn visit<S>(&mut self, serializer: &mut S) -> ::std::result::Result<Option<()>, S::Error>
where S: ::serde::ser::Serializer,
{
match self.state {
$arms
_ => Ok(None)
loop {
match self.state {
$arms
_ => { return Ok(None); }
}
}
}

View File

@ -39,6 +39,20 @@ struct SkipSerializingFields<A: default::Default> {
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]
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::<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,
]
);
}