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:
parent
de89f95f31
commit
001cb7ab01
@ -170,6 +170,7 @@ pub struct FieldAttrs {
|
||||
skip_serializing_field_if_empty: bool,
|
||||
skip_serializing_field_if_none: bool,
|
||||
default_expr_if_missing: Option<P<ast::Expr>>,
|
||||
serialize_with: Option<P<ast::Expr>>,
|
||||
}
|
||||
|
||||
impl FieldAttrs {
|
||||
@ -194,6 +195,7 @@ impl FieldAttrs {
|
||||
skip_serializing_field_if_empty: false,
|
||||
skip_serializing_field_if_none: false,
|
||||
default_expr_if_missing: None,
|
||||
serialize_with: None,
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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(
|
||||
meta_item.span,
|
||||
@ -324,6 +338,10 @@ impl FieldAttrs {
|
||||
pub fn skip_serializing_field_if_none(&self) -> bool {
|
||||
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()
|
||||
})
|
||||
}
|
||||
|
||||
/// 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,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -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,
|
||||
serializer.$serializer_method($key_expr, &self.value.$name)
|
||||
serializer.$serializer_method($key_expr, $field_expr)
|
||||
);
|
||||
|
||||
quote_arm!(cx,
|
||||
|
@ -1,3 +1,6 @@
|
||||
use std::default::Default;
|
||||
use serde::{Serialize, Serializer};
|
||||
|
||||
use token::{
|
||||
Error,
|
||||
Token,
|
||||
@ -11,12 +14,25 @@ trait Trait {
|
||||
fn my_default() -> Self;
|
||||
|
||||
fn should_skip(&self) -> bool;
|
||||
|
||||
fn serialize_with<S>(&self, ser: &mut S) -> Result<(), S::Error>
|
||||
where S: Serializer;
|
||||
}
|
||||
|
||||
impl Trait for i32 {
|
||||
fn my_default() -> 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)]
|
||||
@ -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,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user