Adds serializer format specific field names

Allows different field names to be used for different external formats.

Field names are specified using the `rename` field attribute, e.g:

    #[serde(rename(xml= "a4", json="a5"))]

Reverts #62

Addresses #61
This commit is contained in:
Hugo Duncan 2015-04-27 18:05:54 -04:00
parent af752ddcb5
commit a935ebe8b9
10 changed files with 256 additions and 58 deletions

View File

@ -1,3 +1,5 @@
use std::collections::HashSet;
use syntax::ast::{ use syntax::ast::{
Ident, Ident,
MetaItem, MetaItem,
@ -353,7 +355,8 @@ fn deserialize_item_enum(
cx, cx,
builder, builder,
enum_def.variants.iter() enum_def.variants.iter()
.map(|variant| builder.expr().str(variant.node.name)) .map(|variant|
field::FieldLit::Global(builder.expr().str(variant.node.name)))
.collect() .collect()
); );
@ -531,7 +534,7 @@ fn deserialize_struct_variant(
fn deserialize_field_visitor( fn deserialize_field_visitor(
cx: &ExtCtxt, cx: &ExtCtxt,
builder: &aster::AstBuilder, builder: &aster::AstBuilder,
field_exprs: Vec<P<ast::Expr>>, field_exprs: Vec<field::FieldLit>,
) -> Vec<P<ast::Item>> { ) -> Vec<P<ast::Item>> {
// Create the field names for the fields. // Create the field names for the fields.
let field_idents: Vec<ast::Ident> = (0 .. field_exprs.len()) let field_idents: Vec<ast::Ident> = (0 .. field_exprs.len())
@ -548,14 +551,80 @@ fn deserialize_field_visitor(
) )
.build(); .build();
let fmts = field_exprs.iter()
.fold(HashSet::new(), |mut set, field_expr|
match field_expr {
&field::FieldLit::Format{ref formats, default: _} => {
for (fmt, _) in formats.iter() {
set.insert(fmt.clone());
};
set
},
_ => set
});
// Match arms to extract a field from a string // Match arms to extract a field from a string
let field_arms: Vec<_> = field_idents.iter() let default_field_arms: Vec<_> = field_idents.iter()
.zip(field_exprs.into_iter()) .zip(field_exprs.iter())
.map(|(field_ident, field_expr)| { .map(|(field_ident, field_expr)| {
quote_arm!(cx, $field_expr => { Ok(__Field::$field_ident) }) match field_expr {
&field::FieldLit::Global(ref expr) =>
quote_arm!(cx, $expr => { Ok(__Field::$field_ident) }),
&field::FieldLit::Format{formats: _, ref default} =>
quote_arm!(cx, $default => { Ok(__Field::$field_ident)})
}
}) })
.collect(); .collect();
let body = if fmts.is_empty() {
quote_expr!(cx,
match value {
$default_field_arms,
_ => Err(::serde::de::Error::unknown_field_error(value)),
})
} else {
let field_arms : Vec<_> = fmts.iter()
.map(|fmt| {
field_idents.iter()
.zip(field_exprs.iter())
.map(|(field_ident, field_expr)| {
match field_expr {
&field::FieldLit::Global(ref expr) =>
quote_arm!(cx,
$expr => { Ok(__Field::$field_ident) }),
&field::FieldLit::Format{ref formats, ref default} => {
let expr = formats.get(fmt).unwrap_or(default);
quote_arm!(cx,
$expr => { Ok(__Field::$field_ident) })}}
})
.collect::<Vec<_>>()
})
.collect();
let fmt_matches : Vec<_> = fmts.iter()
.zip(field_arms.iter())
.map(|(ref fmt, ref arms)| {
quote_arm!(cx, $fmt => {
match value {
$arms,
_ => {
Err(::serde::de::Error::unknown_field_error(value))
}
}})
})
.collect();
quote_expr!(cx,
match D::fmt() {
$fmt_matches,
_ => match value {
$default_field_arms,
_ => Err(::serde::de::Error::unknown_field_error(value)),
}
})
};
vec![ vec![
field_enum, field_enum,
@ -565,22 +634,26 @@ fn deserialize_field_visitor(
fn deserialize<D>(deserializer: &mut D) -> ::std::result::Result<__Field, D::Error> fn deserialize<D>(deserializer: &mut D) -> ::std::result::Result<__Field, D::Error>
where D: ::serde::de::Deserializer, where D: ::serde::de::Deserializer,
{ {
struct __FieldVisitor; use std::marker::PhantomData;
impl ::serde::de::Visitor for __FieldVisitor { struct __FieldVisitor<D> {
phantom: PhantomData<D>
}
impl<D> ::serde::de::Visitor for __FieldVisitor<D>
where D: ::serde::de::Deserializer
{
type Value = __Field; type Value = __Field;
fn visit_str<E>(&mut self, value: &str) -> ::std::result::Result<__Field, E> fn visit_str<E>(&mut self, value: &str) -> ::std::result::Result<__Field, E>
where E: ::serde::de::Error, where E: ::serde::de::Error,
{ {
match value { $body
$field_arms
_ => Err(::serde::de::Error::unknown_field_error(value)),
}
} }
} }
deserializer.visit(__FieldVisitor) deserializer.visit(
__FieldVisitor::<D>{ phantom: PhantomData })
} }
} }
).unwrap(), ).unwrap(),
@ -596,7 +669,7 @@ fn deserialize_struct_visitor(
let field_visitor = deserialize_field_visitor( let field_visitor = deserialize_field_visitor(
cx, cx,
builder, builder,
field::struct_field_strs(cx, builder, struct_def, field::Direction::Deserialize), field::struct_field_strs(cx, builder, struct_def),
); );
let visit_map_expr = deserialize_map( let visit_map_expr = deserialize_map(
@ -639,11 +712,16 @@ fn deserialize_map(
let extract_values: Vec<P<ast::Stmt>> = field_names.iter() let extract_values: Vec<P<ast::Stmt>> = field_names.iter()
.zip(struct_def.fields.iter()) .zip(struct_def.fields.iter())
.map(|(field_name, field)| { .map(|(field_name, field)| {
let rename = field::field_rename(field, &field::Direction::Deserialize); let rename = field::field_rename(builder, field);
let name_str = match (rename, field.node.kind) { let name_str = match (rename, field.node.kind) {
(Some(rename), _) => builder.expr().build_lit(P(rename.clone())), (field::Rename::Global(rename), _)
(None, ast::NamedField(name, _)) => builder.expr().str(name), => builder.expr().build_lit(P(rename.clone())),
(None, ast::UnnamedField(_)) => panic!("struct contains unnamed fields"), (field::Rename::None, ast::NamedField(name, _))
=> builder.expr().str(name),
(field::Rename::None, ast::UnnamedField(_))
=> panic!("struct contains unnamed fields"),
(field::Rename::Format(renames), _)
=> builder.expr().str("fixme"),
}; };
let missing_expr = if field::default_value(field) { let missing_expr = if field::default_value(field) {

View File

@ -1,3 +1,5 @@
use std::collections::HashMap;
use syntax::ast; use syntax::ast;
use syntax::attr; use syntax::attr;
use syntax::ext::base::ExtCtxt; use syntax::ext::base::ExtCtxt;
@ -5,19 +7,17 @@ use syntax::ptr::P;
use aster; use aster;
pub enum Direction { pub enum Rename<'a> {
Serialize, None,
Deserialize, Global(&'a ast::Lit),
// Format(HashMap<InternedString, &'a ast::Lit>)
Format(HashMap<P<ast::Expr>, &'a ast::Lit>)
} }
pub fn field_rename<'a>( pub fn field_rename<'a>(
builder: &aster::AstBuilder,
field: &'a ast::StructField, field: &'a ast::StructField,
direction: &Direction, ) -> Rename<'a> {
) -> Option<&'a ast::Lit> {
let dir_attr = match *direction {
Direction::Serialize => "rename_serialize",
Direction::Deserialize => "rename_deserialize",
};
field.node.attrs.iter() field.node.attrs.iter()
.find(|sa| { .find(|sa| {
if let ast::MetaList(ref n, _) = sa.node.value.node { if let ast::MetaList(ref n, _) = sa.node.value.node {
@ -30,34 +30,89 @@ pub fn field_rename<'a>(
if let ast::MetaList(_, ref vals) = sa.node.value.node { if let ast::MetaList(_, ref vals) = sa.node.value.node {
attr::mark_used(&sa); attr::mark_used(&sa);
vals.iter().fold(None, |v, mi| { vals.iter().fold(None, |v, mi| {
if let ast::MetaNameValue(ref n, ref lit) = mi.node { match mi.node {
if n == &"rename" || n == &dir_attr { ast::MetaNameValue(ref n, ref lit) => {
Some(lit) if n == &"rename" {
Some(Rename::Global(lit))
} else { } else {
v v
} }
},
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((// (n.to_owned(), lit)
builder.expr().str(n),
lit
)),
_ => None
}));
Some(Rename::Format(m))
} else { } else {
v v
} }
},
_ => {v}
}
}) })
} else { } else {
None None
} }
}) })
.unwrap_or(Rename::None)
}
pub enum FieldLit {
Global(P<ast::Expr>),
Format{
formats: HashMap<P<ast::Expr>, P<ast::Expr>>,
default: P<ast::Expr>,
}
} }
pub fn struct_field_strs( pub fn struct_field_strs(
cx: &ExtCtxt, cx: &ExtCtxt,
builder: &aster::AstBuilder, builder: &aster::AstBuilder,
struct_def: &ast::StructDef, struct_def: &ast::StructDef,
direction: Direction, ) -> Vec<FieldLit> {
) -> Vec<P<ast::Expr>> {
struct_def.fields.iter() struct_def.fields.iter()
.map(|field| { .map(|field| {
match field_rename(field, &direction) { match field_rename(builder, field) {
Some(rename) => builder.expr().build_lit(P(rename.clone())), Rename::Global(rename) =>
None => { FieldLit::Global(
match field.node.kind { builder.expr().build_lit(P(rename.clone()))),
Rename::Format(renames) => {
let mut res = HashMap::new();
res.extend(
renames.into_iter()
.map(|(k,v)|
(k, builder.expr().build_lit(P(v.clone())))));
FieldLit::Format{
formats: res,
default: default_field(cx, builder, field.node.kind),
}
},
Rename::None => {
FieldLit::Global(
default_field(cx, builder, field.node.kind))
}
}
})
.collect()
}
fn default_field(
cx: &ExtCtxt,
builder: &aster::AstBuilder,
kind: ast::StructFieldKind,
) -> P<ast::Expr> {
match kind {
ast::NamedField(name, _) => { ast::NamedField(name, _) => {
builder.expr().str(name) builder.expr().str(name)
} }
@ -66,10 +121,7 @@ pub fn struct_field_strs(
} }
} }
} }
}
})
.collect()
}
pub fn default_value(field: &ast::StructField) -> bool { pub fn default_value(field: &ast::StructField) -> bool {
field.node.attrs.iter() field.node.attrs.iter()

View File

@ -13,7 +13,7 @@ use syntax::ptr::P;
use aster; use aster;
use field::{Direction, struct_field_strs}; use field::{FieldLit, struct_field_strs};
pub fn expand_derive_serialize( pub fn expand_derive_serialize(
cx: &mut ExtCtxt, cx: &mut ExtCtxt,
@ -517,12 +517,29 @@ fn serialize_struct_visitor<I>(
{ {
let len = struct_def.fields.len(); let len = struct_def.fields.len();
let key_exprs = struct_field_strs(cx, builder, struct_def, Direction::Serialize); let key_exprs = struct_field_strs(cx, builder, struct_def);
let arms: Vec<ast::Arm> = key_exprs.iter() let arms: Vec<ast::Arm> = key_exprs.into_iter()
.zip(value_exprs) .zip(value_exprs)
.enumerate() .enumerate()
.map(|(i, (key_expr, value_expr))| { .map(|(i, (field, value_expr))| {
let key_expr = match field {
FieldLit::Global(x) => x,
FieldLit::Format{formats, default} => {
let arms = formats.iter()
.map(|(fmt, lit)| {
quote_arm!(cx, $fmt => { $lit })
})
.collect::<Vec<_>>();
quote_expr!(cx,
{
match S::fmt() {
$arms,
_ => $default
}
})
},
};
quote_arm!(cx, quote_arm!(cx,
$i => { $i => {
self.state += 1; self.state += 1;

View File

@ -113,6 +113,10 @@ pub trait Deserializer {
{ {
self.visit(visitor) self.visit(visitor)
} }
fn fmt() -> &'static str {
""
}
} }
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////

View File

@ -463,6 +463,11 @@ impl<Iter> de::Deserializer for Deserializer<Iter>
Err(self.error(ErrorCode::ExpectedSomeValue)) Err(self.error(ErrorCode::ExpectedSomeValue))
} }
} }
#[inline]
fn fmt() -> &'static str {
"json"
}
} }
struct SeqVisitor<'a, Iter: 'a + Iterator<Item=io::Result<u8>>> { struct SeqVisitor<'a, Iter: 'a + Iterator<Item=io::Result<u8>>> {

View File

@ -256,6 +256,11 @@ impl<W, F> ser::Serializer for Serializer<W, F>
try!(self.formatter.colon(&mut self.writer)); try!(self.formatter.colon(&mut self.writer));
value.serialize(self) value.serialize(self)
} }
#[inline]
fn fmt() -> &'static str {
"json"
}
} }
pub trait Formatter { pub trait Formatter {

View File

@ -571,6 +571,11 @@ impl ser::Serializer for Serializer {
Ok(()) Ok(())
} }
#[inline]
fn fmt() -> &'static str {
"value"
}
} }
pub struct Deserializer { pub struct Deserializer {
@ -677,6 +682,11 @@ impl de::Deserializer for Deserializer {
None => Ok(value) None => Ok(value)
} }
} }
#[inline]
fn fmt() -> &'static str {
"value"
}
} }
struct SeqDeserializer<'a> { struct SeqDeserializer<'a> {

View File

@ -179,6 +179,10 @@ pub trait Serializer {
fn visit_map_elt<K, V>(&mut self, key: K, value: V) -> Result<(), Self::Error> fn visit_map_elt<K, V>(&mut self, key: K, value: V) -> Result<(), Self::Error>
where K: Serialize, where K: Serialize,
V: Serialize; V: Serialize;
fn fmt() -> &'static str {
""
}
} }
pub trait SeqVisitor { pub trait SeqVisitor {

View File

@ -21,9 +21,9 @@ struct Rename {
} }
#[derive(Debug, PartialEq, Serialize, Deserialize)] #[derive(Debug, PartialEq, Serialize, Deserialize)]
struct DirectionRename { struct FormatRename {
a1: i32, a1: i32,
#[serde(rename_serialize="a3", rename_deserialize="a4")] #[serde(rename(xml= "a4", json="a5"))]
a2: i32, a2: i32,
} }
@ -47,11 +47,11 @@ fn test_rename() {
} }
#[test] #[test]
fn test_direction_rename() { fn test_format_rename() {
let value = DirectionRename { a1: 1, a2: 2 }; let value = FormatRename { a1: 1, a2: 2 };
let serialized_value = json::to_string(&value).unwrap(); let serialized_value = json::to_string(&value).unwrap();
assert_eq!(serialized_value, "{\"a1\":1,\"a3\":2}"); assert_eq!(serialized_value, "{\"a1\":1,\"a5\":2}");
let deserialized_value = json::from_str("{\"a1\":1,\"a4\":2}").unwrap(); let deserialized_value = json::from_str("{\"a1\":1,\"a5\":2}").unwrap();
assert_eq!(value, deserialized_value); assert_eq!(value, deserialized_value);
} }

View File

@ -1024,7 +1024,7 @@ fn test_missing_field() {
fn test_missing_renamed_field() { fn test_missing_renamed_field() {
#[derive(Debug, PartialEq, Deserialize)] #[derive(Debug, PartialEq, Deserialize)]
struct Foo { struct Foo {
#[serde(rename_deserialize="y")] #[serde(rename="y")]
x: Option<u32>, x: Option<u32>,
} }
@ -1042,3 +1042,26 @@ fn test_missing_renamed_field() {
))).unwrap(); ))).unwrap();
assert_eq!(value, Foo { x: Some(5) }); assert_eq!(value, Foo { x: Some(5) });
} }
#[test]
fn test_missing_fmt_renamed_field() {
#[derive(Debug, PartialEq, Deserialize)]
struct Foo {
#[serde(rename(json="y", value="z"))]
x: Option<u32>,
}
let value: Foo = from_str("{}").unwrap();
assert_eq!(value, Foo { x: None });
let value: Foo = from_str("{\"y\": 5}").unwrap();
assert_eq!(value, Foo { x: Some(5) });
let value: Foo = from_value(Value::Object(treemap!())).unwrap();
assert_eq!(value, Foo { x: None });
let value : Foo = from_value(Value::Object(treemap!(
"z".to_string() => Value::I64(5)
))).unwrap();
assert_eq!(value, Foo { x: Some(5) });
}