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_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,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user