mirror of
https://github.com/pezkuwichain/serde.git
synced 2026-06-13 17:11:02 +00:00
feat(codegen): Add #[serde(deserialize_with="...")]
This allows a field to be deserialized with an expression instead of the default deserializer. This simplifies deserializing a struct or enum that contains an external type that doesn't implement `serde::Deserialize`. This expression is passed a variable `deserializer` that needs to be used to deserialize the expression.
This commit is contained in:
@@ -171,6 +171,7 @@ pub struct FieldAttrs {
|
|||||||
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>>,
|
serialize_with: Option<P<ast::Expr>>,
|
||||||
|
deserialize_with: Option<P<ast::Expr>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FieldAttrs {
|
impl FieldAttrs {
|
||||||
@@ -196,6 +197,7 @@ impl FieldAttrs {
|
|||||||
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,
|
serialize_with: None,
|
||||||
|
deserialize_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) {
|
||||||
@@ -271,6 +273,18 @@ impl FieldAttrs {
|
|||||||
field_attrs.serialize_with = Some(expr);
|
field_attrs.serialize_with = Some(expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse `#[serde(deserialize_with="...")]`
|
||||||
|
ast::MetaItemKind::NameValue(ref name, ref lit) if name == &"deserialize_with" => {
|
||||||
|
let expr = wrap_deserialize_with(
|
||||||
|
cx,
|
||||||
|
&field.node.ty,
|
||||||
|
generics,
|
||||||
|
try!(parse_lit_into_expr(cx, name, lit)),
|
||||||
|
);
|
||||||
|
|
||||||
|
field_attrs.deserialize_with = Some(expr);
|
||||||
|
}
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
cx.span_err(
|
cx.span_err(
|
||||||
meta_item.span,
|
meta_item.span,
|
||||||
@@ -342,6 +356,10 @@ impl FieldAttrs {
|
|||||||
pub fn serialize_with(&self) -> Option<&P<ast::Expr>> {
|
pub fn serialize_with(&self) -> Option<&P<ast::Expr>> {
|
||||||
self.serialize_with.as_ref()
|
self.serialize_with.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn deserialize_with(&self) -> Option<&P<ast::Expr>> {
|
||||||
|
self.deserialize_with.as_ref()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -515,3 +533,58 @@ fn wrap_serialize_with(cx: &ExtCtxt,
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This function wraps the expression in `#[serde(deserialize_with="...")]` in a trait to prevent
|
||||||
|
/// it from accessing the internal `Deserialize` state.
|
||||||
|
fn wrap_deserialize_with(cx: &ExtCtxt,
|
||||||
|
field_ty: &P<ast::Ty>,
|
||||||
|
generics: &ast::Generics,
|
||||||
|
expr: P<ast::Expr>) -> P<ast::Expr> {
|
||||||
|
let builder = AstBuilder::new();
|
||||||
|
|
||||||
|
let fn_generics = builder.from_generics(generics.clone())
|
||||||
|
.ty_param("__D")
|
||||||
|
.bound()
|
||||||
|
.trait_(
|
||||||
|
builder.path()
|
||||||
|
.global()
|
||||||
|
.ids(&["serde", "de", "Deserializer"])
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
.build()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Quasi-quoting doesn't do a great job of expanding generics into paths, so manually build it.
|
||||||
|
let ty_path = AstBuilder::new().path()
|
||||||
|
.segment("__SerdeDeserializeWithStruct")
|
||||||
|
.with_generics(generics.clone())
|
||||||
|
.build()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let fn_where_clause = &fn_generics.where_clause;
|
||||||
|
let where_clause = &generics.where_clause;
|
||||||
|
|
||||||
|
quote_expr!(cx, {
|
||||||
|
fn __serde_deserialize_with $fn_generics(deserializer: &mut __D)
|
||||||
|
-> Result<$field_ty, __D::Error> $fn_where_clause {
|
||||||
|
$expr
|
||||||
|
}
|
||||||
|
|
||||||
|
struct __SerdeDeserializeWithStruct $generics $where_clause {
|
||||||
|
value: $field_ty,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl $generics ::serde::de::Deserialize for $ty_path $where_clause {
|
||||||
|
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error>
|
||||||
|
where D: ::serde::de::Deserializer
|
||||||
|
{
|
||||||
|
let value = try!(__serde_deserialize_with(deserializer));
|
||||||
|
Ok(__SerdeDeserializeWithStruct { value: value })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let value: $ty_path = try!(visitor.visit_value());
|
||||||
|
Ok(value.value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
+17
-13
@@ -991,6 +991,12 @@ fn deserialize_map(
|
|||||||
.map(|i| builder.id(format!("__field{}", i)))
|
.map(|i| builder.id(format!("__field{}", i)))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
let field_attrs: Vec<_> = try!(
|
||||||
|
fields.iter()
|
||||||
|
.map(|field| attr::FieldAttrs::from_field(cx, container_ty, generics, field))
|
||||||
|
.collect()
|
||||||
|
);
|
||||||
|
|
||||||
// Declare each field.
|
// Declare each field.
|
||||||
let let_values: Vec<ast::Stmt> = field_names.iter()
|
let let_values: Vec<ast::Stmt> = field_names.iter()
|
||||||
.map(|field_name| quote_stmt!(cx, let mut $field_name = None;).unwrap())
|
.map(|field_name| quote_stmt!(cx, let mut $field_name = None;).unwrap())
|
||||||
@@ -1007,26 +1013,24 @@ fn deserialize_map(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Match arms to extract a value for a field.
|
// Match arms to extract a value for a field.
|
||||||
let value_arms: Vec<ast::Arm> = field_names.iter()
|
let value_arms = field_attrs.iter().zip(field_names.iter())
|
||||||
.map(|field_name| {
|
.map(|(field_attr, field_name)| {
|
||||||
|
let expr = match field_attr.deserialize_with() {
|
||||||
|
Some(expr) => expr.clone(),
|
||||||
|
None => quote_expr!(cx, visitor.visit_value()),
|
||||||
|
};
|
||||||
|
|
||||||
quote_arm!(cx,
|
quote_arm!(cx,
|
||||||
__Field::$field_name => {
|
__Field::$field_name => {
|
||||||
$field_name = Some(try!(visitor.visit_value()));
|
$field_name = Some(try!($expr));
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.chain(ignored_arm.into_iter())
|
.chain(ignored_arm.into_iter())
|
||||||
.collect();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let extract_values = fields.iter()
|
let extract_values = field_attrs.iter().zip(field_names.iter())
|
||||||
.zip(field_names.iter())
|
.map(|(field_attr, field_name)| {
|
||||||
.map(|(field, field_name)| {
|
|
||||||
let field_attr = try!(
|
|
||||||
attr::FieldAttrs::from_field(cx,
|
|
||||||
container_ty,
|
|
||||||
generics,
|
|
||||||
field)
|
|
||||||
);
|
|
||||||
let missing_expr = field_attr.expr_is_missing();
|
let missing_expr = field_attr.expr_is_missing();
|
||||||
|
|
||||||
Ok(quote_stmt!(cx,
|
Ok(quote_stmt!(cx,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
use serde::{Serialize, Serializer};
|
use serde::{Serialize, Serializer, Deserialize, Deserializer};
|
||||||
|
|
||||||
use token::{
|
use token::{
|
||||||
Error,
|
Error,
|
||||||
@@ -10,13 +10,16 @@ use token::{
|
|||||||
assert_de_tokens_error
|
assert_de_tokens_error
|
||||||
};
|
};
|
||||||
|
|
||||||
trait Trait {
|
trait Trait: Sized {
|
||||||
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>
|
fn serialize_with<S>(&self, ser: &mut S) -> Result<(), S::Error>
|
||||||
where S: Serializer;
|
where S: Serializer;
|
||||||
|
|
||||||
|
fn deserialize_with<D>(de: &mut D) -> Result<Self, D::Error>
|
||||||
|
where D: Deserializer;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Trait for i32 {
|
impl Trait for i32 {
|
||||||
@@ -33,6 +36,16 @@ impl Trait for i32 {
|
|||||||
false.serialize(ser)
|
false.serialize(ser)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn deserialize_with<D>(de: &mut D) -> Result<Self, D::Error>
|
||||||
|
where D: Deserializer
|
||||||
|
{
|
||||||
|
if try!(Deserialize::deserialize(de)) {
|
||||||
|
Ok(123)
|
||||||
|
} else {
|
||||||
|
Ok(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||||
@@ -622,3 +635,105 @@ fn test_serialize_with_enum() {
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Deserialize)]
|
||||||
|
struct DeserializeWithStruct<B> where B: Trait {
|
||||||
|
a: i8,
|
||||||
|
#[serde(deserialize_with="Trait::deserialize_with(deserializer)")]
|
||||||
|
b: B,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_deserialize_with_struct() {
|
||||||
|
assert_de_tokens(
|
||||||
|
&DeserializeWithStruct {
|
||||||
|
a: 1,
|
||||||
|
b: 2,
|
||||||
|
},
|
||||||
|
vec![
|
||||||
|
Token::StructStart("DeserializeWithStruct", Some(2)),
|
||||||
|
|
||||||
|
Token::StructSep,
|
||||||
|
Token::Str("a"),
|
||||||
|
Token::I8(1),
|
||||||
|
|
||||||
|
Token::StructSep,
|
||||||
|
Token::Str("b"),
|
||||||
|
Token::Bool(false),
|
||||||
|
|
||||||
|
Token::StructEnd,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_de_tokens(
|
||||||
|
&DeserializeWithStruct {
|
||||||
|
a: 1,
|
||||||
|
b: 123,
|
||||||
|
},
|
||||||
|
vec![
|
||||||
|
Token::StructStart("DeserializeWithStruct", Some(2)),
|
||||||
|
|
||||||
|
Token::StructSep,
|
||||||
|
Token::Str("a"),
|
||||||
|
Token::I8(1),
|
||||||
|
|
||||||
|
Token::StructSep,
|
||||||
|
Token::Str("b"),
|
||||||
|
Token::Bool(true),
|
||||||
|
|
||||||
|
Token::StructEnd,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Deserialize)]
|
||||||
|
enum DeserializeWithEnum<B> where B: Trait {
|
||||||
|
Struct {
|
||||||
|
a: i8,
|
||||||
|
#[serde(deserialize_with="Trait::deserialize_with(deserializer)")]
|
||||||
|
b: B,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_deserialize_with_enum() {
|
||||||
|
assert_de_tokens(
|
||||||
|
&DeserializeWithEnum::Struct {
|
||||||
|
a: 1,
|
||||||
|
b: 2,
|
||||||
|
},
|
||||||
|
vec![
|
||||||
|
Token::EnumMapStart("DeserializeWithEnum", "Struct", Some(2)),
|
||||||
|
|
||||||
|
Token::EnumMapSep,
|
||||||
|
Token::Str("a"),
|
||||||
|
Token::I8(1),
|
||||||
|
|
||||||
|
Token::EnumMapSep,
|
||||||
|
Token::Str("b"),
|
||||||
|
Token::Bool(false),
|
||||||
|
|
||||||
|
Token::EnumMapEnd,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_de_tokens(
|
||||||
|
&DeserializeWithEnum::Struct {
|
||||||
|
a: 1,
|
||||||
|
b: 123,
|
||||||
|
},
|
||||||
|
vec![
|
||||||
|
Token::EnumMapStart("DeserializeWithEnum", "Struct", Some(2)),
|
||||||
|
|
||||||
|
Token::EnumMapSep,
|
||||||
|
Token::Str("a"),
|
||||||
|
Token::I8(1),
|
||||||
|
|
||||||
|
Token::EnumMapSep,
|
||||||
|
Token::Str("b"),
|
||||||
|
Token::Bool(true),
|
||||||
|
|
||||||
|
Token::EnumMapEnd,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user