Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow specifying what name to use for a field when using Encodable/Decodable. #15795

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions src/libserialize/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,41 @@ fn main() {
}
```

If you need to work with a JSON object which has keys that are not Rust
identifiers, you can tag each such field with the `encoded_name` attribute
to specify the name to use when serializing/deserializing.

```rust
extern crate serialize;
use serialize::json;

#[deriving(Encodable, Decodable)]
struct Data {
metric: String,
#[encoded_name = "metric-value"]
value: int
}

fn main() {
let input = r#"{"metric":"foo","metric-value":44}"#;

// Decode the input JSON
let data: Data = json::decode(input).unwrap();

// Modify the input
let data = Data {
value: 32,
..data
};

// Encode it as JSON again
let out: String = json::encode(&data);

println!("data: {}", out);
// data: {"metric":"foo","metric-value":32}
}
```

## Using the `ToJson` trait

The examples above use the `ToJson` trait to generate the JSON string, which required
Expand Down Expand Up @@ -3319,6 +3354,31 @@ mod tests {
assert_eq!(None::<int>.to_json(), Null);
}

#[deriving(Encodable, Decodable, PartialEq, Show)]
struct Foo {
key: String,
#[encoded_name = "another-key"]
another_key: String,
metric: int,
#[encoded_name = "metric-2"]
another_metric: int
}

#[test]
fn test_encoded_name_attribute() {
use super::{encode, decode};

let s = r#"{"key":"foo","another-key":"bar","metric":12,"metric-2":21}"#;
let f = Foo {
key: "foo".to_string(),
another_key: "bar".to_string(),
metric: 12,
another_metric: 21
};
assert_eq!(encode(&f), s.to_string());
assert_eq!(decode::<Foo>(s).unwrap(), f);
}

#[bench]
fn bench_streaming_small(b: &mut Bencher) {
b.iter( || {
Expand Down
5 changes: 4 additions & 1 deletion src/libsyntax/attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,10 @@ pub fn first_attr_value_str_by_name(attrs: &[Attribute], name: &str)
-> Option<InternedString> {
attrs.iter()
.find(|at| at.check_name(name))
.and_then(|at| at.value_str())
.and_then(|at| {
mark_used(at);
at.value_str()
})
}

pub fn last_meta_item_value_str_by_name(items: &[Gc<MetaItem>], name: &str)
Expand Down
6 changes: 5 additions & 1 deletion src/libsyntax/ext/deriving/decodable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ encodable.rs for more.
*/

use ast::{MetaItem, Item, Expr, MutMutable, Ident};
use attr;
use codemap::Span;
use ext::base::ExtCtxt;
use ext::build::AstBuilder;
Expand Down Expand Up @@ -76,7 +77,7 @@ fn decodable_substructure(cx: &mut ExtCtxt, trait_span: Span,
let lambdadecode = cx.lambda_expr_1(trait_span, calldecode, blkarg);

return match *substr.fields {
StaticStruct(_, ref summary) => {
StaticStruct(struct_def, ref summary) => {
let nfields = match *summary {
Unnamed(ref fields) => fields.len(),
Named(ref fields) => fields.len()
Expand All @@ -88,6 +89,9 @@ fn decodable_substructure(cx: &mut ExtCtxt, trait_span: Span,
substr.type_ident,
summary,
|cx, span, name, field| {
let attrs = struct_def.fields[field].node.attrs.as_slice();
let name = attr::first_attr_value_str_by_name(attrs, "encoded_name")
.unwrap_or(name);
cx.expr_try(span,
cx.expr_method_call(span, blkdecoder, read_struct_field,
vec!(cx.expr_str(span, name),
Expand Down
6 changes: 5 additions & 1 deletion src/libsyntax/ext/deriving/encodable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
//! ```

use ast::{MetaItem, Item, Expr, ExprRet, MutMutable, LitNil};
use attr;
use codemap::Span;
use ext::base::ExtCtxt;
use ext::build::AstBuilder;
Expand Down Expand Up @@ -144,13 +145,16 @@ fn encodable_substructure(cx: &mut ExtCtxt, trait_span: Span,
let mut stmts = Vec::new();
let last = fields.len() - 1;
for (i, &FieldInfo {
ref attrs,
name,
self_,
span,
..
}) in fields.iter().enumerate() {
let name = match name {
Some(id) => token::get_ident(id),
Some(id) =>
attr::first_attr_value_str_by_name(attrs.as_slice(), "encoded_name")
.unwrap_or(token::get_ident(id)),
None => {
token::intern_and_get_ident(format!("_field{}",
i).as_slice())
Expand Down
46 changes: 29 additions & 17 deletions src/libsyntax/ext/deriving/generic/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@
//!
//! ~~~text
//! Struct(~[FieldInfo {
//! span: <span of x>
//! attrs: <any attrs on x>,
//! span: <span of x>,
//! name: Some(<ident of x>),
//! self_: <expr for &self.x>,
//! other: ~[<expr for &other.x]
Expand All @@ -114,6 +115,7 @@
//!
//! ~~~text
//! Struct(~[FieldInfo {
//! attrs: <empty vec>,
//! span: <span of `int`>,
//! name: None,
//! <expr for &a>
Expand All @@ -129,7 +131,8 @@
//! ~~~text
//! EnumMatching(0, <ast::Variant for C0>,
//! ~[FieldInfo {
//! span: <span of int>
//! attrs: <empty vec>,
//! span: <span of int>,
//! name: None,
//! self_: <expr for &a>,
//! other: ~[<expr for &b>]
Expand All @@ -141,7 +144,8 @@
//! ~~~text
//! EnumMatching(1, <ast::Variant for C1>,
//! ~[FieldInfo {
//! span: <span of x>
//! attrs: <any attrs on x>,
//! span: <span of x>,
//! name: Some(<ident of x>),
//! self_: <expr for &self.x>,
//! other: ~[<expr for &other.x>]
Expand Down Expand Up @@ -259,6 +263,10 @@ pub struct Substructure<'a> {

/// Summary of the relevant parts of a struct/enum field.
pub struct FieldInfo {
/// The attributes on the field in the definition
/// for normal structs/struct enum variants
pub attrs: Vec<ast::Attribute>,
/// The field's span
pub span: Span,
/// None for tuple structs/normal enum variants, Some for normal
/// structs/struct enum variants.
Expand Down Expand Up @@ -724,13 +732,14 @@ impl<'a> MethodDef<'a> {
raw_fields.get(0)
.iter()
.enumerate()
.map(|(i, &(span, opt_id, field))| {
.map(|(i, &(ref attrs, span, opt_id, field))| {
let other_fields = raw_fields.tail().iter().map(|l| {
match l.get(i) {
&(_, _, ex) => ex
&(_, _, _, ex) => ex
}
}).collect();
FieldInfo {
attrs: attrs.clone(),
span: span,
name: opt_id,
self_: field,
Expand Down Expand Up @@ -900,7 +909,7 @@ impl<'a> MethodDef<'a> {

// These self_pats have form Variant1, Variant2, ...
let self_pats : Vec<(Gc<ast::Pat>,
Vec<(Span, Option<Ident>, Gc<Expr>)>)>;
Vec<(Vec<ast::Attribute>, Span, Option<Ident>, Gc<Expr>)>)>;
self_pats = self_arg_names.iter()
.map(|self_arg_name|
trait_.create_enum_variant_pattern(
Expand Down Expand Up @@ -934,7 +943,7 @@ impl<'a> MethodDef<'a> {

field_tuples = self_arg_fields.iter().enumerate()
// For each arg field of self, pull out its getter expr ...
.map(|(field_index, &(sp, opt_ident, self_getter_expr))| {
.map(|(field_index, &(ref attrs, sp, opt_ident, self_getter_expr))| {
// ... but FieldInfo also wants getter expr
// for matching other arguments of Self type;
// so walk across the *other* self_pats and
Expand All @@ -944,7 +953,7 @@ impl<'a> MethodDef<'a> {
let others = self_pats.tail().iter()
.map(|&(_pat, ref fields)| {

let &(_, _opt_ident, other_getter_expr) =
let &(_, _, _opt_ident, other_getter_expr) =
fields.get(field_index);

// All Self args have same variant, so
Expand All @@ -956,10 +965,12 @@ impl<'a> MethodDef<'a> {
other_getter_expr
}).collect::<Vec<Gc<Expr>>>();

FieldInfo { span: sp,
name: opt_ident,
self_: self_getter_expr,
other: others,
FieldInfo {
attrs: attrs.clone(),
span: sp,
name: opt_ident,
self_: self_getter_expr,
other: others,
}
}).collect::<Vec<FieldInfo>>();

Expand Down Expand Up @@ -1204,7 +1215,8 @@ impl<'a> TraitDef<'a> {
struct_def: &StructDef,
prefix: &str,
mutbl: ast::Mutability)
-> (Gc<ast::Pat>, Vec<(Span, Option<Ident>, Gc<Expr>)>) {
-> (Gc<ast::Pat>, Vec<(Vec<ast::Attribute>, Span, Option<Ident>, Gc<Expr>)>) {

if struct_def.fields.is_empty() {
return (
cx.pat_ident_binding_mode(
Expand Down Expand Up @@ -1239,15 +1251,15 @@ impl<'a> TraitDef<'a> {
paths.push(codemap::Spanned{span: sp, node: ident});
let val = cx.expr(
sp, ast::ExprParen(cx.expr_deref(sp, cx.expr_path(cx.path_ident(sp,ident)))));
ident_expr.push((sp, opt_id, val));
ident_expr.push((struct_field.node.attrs.clone(), sp, opt_id, val));
}

let subpats = self.create_subpatterns(cx, paths, mutbl);

// struct_type is definitely not Unknown, since struct_def.fields
// must be nonempty to reach here
let pattern = if struct_type == Record {
let field_pats = subpats.iter().zip(ident_expr.iter()).map(|(&pat, &(_, id, _))| {
let field_pats = subpats.iter().zip(ident_expr.iter()).map(|(&pat, &(_, _, id, _))| {
// id is guaranteed to be Some
ast::FieldPat { ident: id.unwrap(), pat: pat }
}).collect();
Expand All @@ -1264,7 +1276,7 @@ impl<'a> TraitDef<'a> {
variant: &ast::Variant,
prefix: &str,
mutbl: ast::Mutability)
-> (Gc<ast::Pat>, Vec<(Span, Option<Ident>, Gc<Expr>)> ) {
-> (Gc<ast::Pat>, Vec<(Vec<ast::Attribute>, Span, Option<Ident>, Gc<Expr>)> ) {
let variant_ident = variant.node.name;
match variant.node.kind {
ast::TupleVariantKind(ref variant_args) => {
Expand All @@ -1285,7 +1297,7 @@ impl<'a> TraitDef<'a> {
paths.push(path1);
let expr_path = cx.expr_path(cx.path_ident(sp, ident));
let val = cx.expr(sp, ast::ExprParen(cx.expr_deref(sp, expr_path)));
ident_expr.push((sp, None, val));
ident_expr.push((Vec::new(), sp, None, val));
}

let subpats = self.create_subpatterns(cx, paths, mutbl);
Expand Down