Skip to content

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

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

Closed
wants to merge 1 commit into from
Closed
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
@@ -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
@@ -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( || {
5 changes: 4 additions & 1 deletion src/libsyntax/attr.rs
Original file line number Diff line number Diff line change
@@ -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)
6 changes: 5 additions & 1 deletion src/libsyntax/ext/deriving/decodable.rs
Original file line number Diff line number Diff line change
@@ -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;
@@ -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()
@@ -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),
6 changes: 5 additions & 1 deletion src/libsyntax/ext/deriving/encodable.rs
Original file line number Diff line number Diff line change
@@ -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;
@@ -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())
46 changes: 29 additions & 17 deletions src/libsyntax/ext/deriving/generic/mod.rs
Original file line number Diff line number Diff line change
@@ -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]
@@ -114,6 +115,7 @@
//!
//! ~~~text
//! Struct(~[FieldInfo {
//! attrs: <empty vec>,
//! span: <span of `int`>,
//! name: None,
//! <expr for &a>
@@ -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>]
@@ -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>]
@@ -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.
@@ -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,
@@ -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(
@@ -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
@@ -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
@@ -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>>();

@@ -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(
@@ -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();
@@ -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) => {
@@ -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);