feat(codegen): Add #[serde(serialize_with="...")]

This allows a field to be serialized with an expression instead
of the default serializer. This simplifies serializing a struct
or enum that contains an external type that doesn't implement
`serde::Serialize`. This expression is passed a variable
`serializer` that needs to be used to serialize the expression.
This commit is contained in:
Erick Tryzelaar 2016-02-15 17:39:46 -08:00
parent de89f95f31
commit 001cb7ab01
3 changed files with 196 additions and 1 deletions

View File

@ -170,6 +170,7 @@ pub struct FieldAttrs {
skip_serializing_field_if_empty: bool, skip_serializing_field_if_empty: bool,
skip_serializing_field_if_none: bool, skip_serializing_field_if_none: bool,
default_expr_if_missing: Option<P<ast::Expr>>, default_expr_if_missing: Option<P<ast::Expr>>,
serialize_with: Option<P<ast::Expr>>,
} }
impl FieldAttrs { impl FieldAttrs {
@ -194,6 +195,7 @@ impl FieldAttrs {
skip_serializing_field_if_empty: false, skip_serializing_field_if_empty: false,
skip_serializing_field_if_none: false, skip_serializing_field_if_none: false,
default_expr_if_missing: None, default_expr_if_missing: None,
serialize_with: None,
}; };
for meta_items in field.node.attrs.iter().filter_map(get_serde_meta_items) { for meta_items in field.node.attrs.iter().filter_map(get_serde_meta_items) {
@ -257,6 +259,18 @@ impl FieldAttrs {
field_attrs.skip_serializing_field_if_empty = true; field_attrs.skip_serializing_field_if_empty = true;
} }
// Parse `#[serde(serialize_with="...")]`
ast::MetaItemKind::NameValue(ref name, ref lit) if name == &"serialize_with" => {
let expr = wrap_serialize_with(
cx,
container_ty,
generics,
try!(parse_lit_into_expr(cx, name, lit)),
);
field_attrs.serialize_with = Some(expr);
}
_ => { _ => {
cx.span_err( cx.span_err(
meta_item.span, meta_item.span,
@ -324,6 +338,10 @@ impl FieldAttrs {
pub fn skip_serializing_field_if_none(&self) -> bool { pub fn skip_serializing_field_if_none(&self) -> bool {
self.skip_serializing_field_if_none self.skip_serializing_field_if_none
} }
pub fn serialize_with(&self) -> Option<&P<ast::Expr>> {
self.serialize_with.as_ref()
}
} }
@ -445,3 +463,55 @@ fn wrap_skip_serializing(cx: &ExtCtxt,
self.value.__serde_should_skip_serializing() self.value.__serde_should_skip_serializing()
}) })
} }
/// This function wraps the expression in `#[serde(serialize_with="...")]` in a trait to
/// prevent it from accessing the internal `Serialize` state.
fn wrap_serialize_with(cx: &ExtCtxt,
container_ty: &P<ast::Ty>,
generics: &ast::Generics,
expr: P<ast::Expr>) -> P<ast::Expr> {
let where_clause = &generics.where_clause;
quote_expr!(cx, {
trait __SerdeSerializeWith {
fn __serde_serialize_with<S>(&self, serializer: &mut S) -> Result<(), S::Error>
where S: ::serde::ser::Serializer;
}
impl<'a, T> __SerdeSerializeWith for &'a T
where T: 'a + __SerdeSerializeWith,
{
fn __serde_serialize_with<S>(&self, serializer: &mut S) -> Result<(), S::Error>
where S: ::serde::ser::Serializer
{
(**self).__serde_serialize_with(serializer)
}
}
impl $generics __SerdeSerializeWith for $container_ty $where_clause {
fn __serde_serialize_with<S>(&self, serializer: &mut S) -> Result<(), S::Error>
where S: ::serde::ser::Serializer
{
$expr
}
}
struct __SerdeSerializeWithStruct<'a, T: 'a> {
value: &'a T,
}
impl<'a, T> ::serde::ser::Serialize for __SerdeSerializeWithStruct<'a, T>
where T: 'a + __SerdeSerializeWith
{
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
where S: ::serde::ser::Serializer
{
self.value.__serde_serialize_with(serializer)
}
}
__SerdeSerializeWithStruct {
value: &self.value,
}
})
}

View File

@ -672,8 +672,13 @@ fn serialize_struct_visitor(
} }
}; };
let field_expr = match field_attr.serialize_with() {
Some(expr) => expr.clone(),
None => quote_expr!(cx, &self.value.$name),
};
let expr = quote_expr!(cx, let expr = quote_expr!(cx,
serializer.$serializer_method($key_expr, &self.value.$name) serializer.$serializer_method($key_expr, $field_expr)
); );
quote_arm!(cx, quote_arm!(cx,

View File

@ -1,3 +1,6 @@
use std::default::Default;
use serde::{Serialize, Serializer};
use token::{ use token::{
Error, Error,
Token, Token,
@ -11,12 +14,25 @@ trait Trait {
fn my_default() -> Self; fn my_default() -> Self;
fn should_skip(&self) -> bool; fn should_skip(&self) -> bool;
fn serialize_with<S>(&self, ser: &mut S) -> Result<(), S::Error>
where S: Serializer;
} }
impl Trait for i32 { impl Trait for i32 {
fn my_default() -> Self { 123 } fn my_default() -> Self { 123 }
fn should_skip(&self) -> bool { *self == 123 } fn should_skip(&self) -> bool { *self == 123 }
fn serialize_with<S>(&self, ser: &mut S) -> Result<(), S::Error>
where S: Serializer
{
if *self == 123 {
true.serialize(ser)
} else {
false.serialize(ser)
}
}
} }
#[derive(Debug, PartialEq, Serialize, Deserialize)] #[derive(Debug, PartialEq, Serialize, Deserialize)]
@ -502,3 +518,107 @@ fn test_skip_serializing_enum() {
] ]
); );
} }
#[derive(Debug, PartialEq, Serialize)]
struct SerializeWithStruct<'a, B> where B: Trait {
a: &'a i8,
#[serde(serialize_with="self.b.serialize_with(serializer)")]
b: B,
}
#[test]
fn test_serialize_with_struct() {
let a = 1;
assert_ser_tokens(
&SerializeWithStruct {
a: &a,
b: 2,
},
&[
Token::StructStart("SerializeWithStruct", Some(2)),
Token::StructSep,
Token::Str("a"),
Token::I8(1),
Token::StructSep,
Token::Str("b"),
Token::Bool(false),
Token::StructEnd,
]
);
assert_ser_tokens(
&SerializeWithStruct {
a: &a,
b: 123,
},
&[
Token::StructStart("SerializeWithStruct", Some(2)),
Token::StructSep,
Token::Str("a"),
Token::I8(1),
Token::StructSep,
Token::Str("b"),
Token::Bool(true),
Token::StructEnd,
]
);
}
#[derive(Debug, PartialEq, Serialize)]
enum SerializeWithEnum<'a, B> where B: Trait {
Struct {
a: &'a i8,
#[serde(serialize_with="self.b.serialize_with(serializer)")]
b: B,
}
}
#[test]
fn test_serialize_with_enum() {
let a = 1;
assert_ser_tokens(
&SerializeWithEnum::Struct {
a: &a,
b: 2,
},
&[
Token::EnumMapStart("SerializeWithEnum", "Struct", Some(2)),
Token::EnumMapSep,
Token::Str("a"),
Token::I8(1),
Token::EnumMapSep,
Token::Str("b"),
Token::Bool(false),
Token::EnumMapEnd,
]
);
assert_ser_tokens(
&SerializeWithEnum::Struct {
a: &a,
b: 123,
},
&[
Token::EnumMapStart("SerializeWithEnum", "Struct", Some(2)),
Token::EnumMapSep,
Token::Str("a"),
Token::I8(1),
Token::EnumMapSep,
Token::Str("b"),
Token::Bool(true),
Token::EnumMapEnd,
]
);
}