From 97528b59cf4a7a107f206e529dc966c4bf7e3a48 Mon Sep 17 00:00:00 2001 From: Erick Tryzelaar Date: Thu, 30 Jul 2015 09:38:09 -0700 Subject: [PATCH] Add support for serializing newtype structs This enables formats like JSON to serialize newtype wrapper structs without the normal object wrapper. Consider types like: ```rust struct Point { x: u32, y: u32, } stuct MyPoint(Point); ``` Before this patch, `MyPoint(1,2)` would get serialized as `[{"x":1,"y":2}]`, but now it gets serialized as `{"x":1,"y"2}`. --- serde/src/de/mod.rs | 21 +++++++++- serde/src/ser/mod.rs | 12 ++++++ serde_codegen/src/de.rs | 71 +++++++++++++++++++++++++++++--- serde_codegen/src/ser.rs | 27 +++++++++--- serde_json/src/de.rs | 9 ++++ serde_json/src/ser.rs | 10 +++++ serde_tests/tests/test_macros.rs | 71 +++++++++++++++++++------------- 7 files changed, 180 insertions(+), 41 deletions(-) diff --git a/serde/src/de/mod.rs b/serde/src/de/mod.rs index e00140b0..219b8a52 100644 --- a/serde/src/de/mod.rs +++ b/serde/src/de/mod.rs @@ -208,8 +208,8 @@ pub trait Deserializer { self.visit(visitor) } - /// This method hints that the `Deserialize` type is expecting a named unit. This allows - /// deserializers to a named unit that aren't tagged as a named unit. + /// This method hints that the `Deserialize` type is expecting a unit struct. This allows + /// deserializers to a unit struct that aren't tagged as a unit struct. #[inline] fn visit_unit_struct(&mut self, _name: &'static str, @@ -219,6 +219,17 @@ pub trait Deserializer { self.visit_unit(visitor) } + /// This method hints that the `Deserialize` type is expecting a newtype struct. This allows + /// deserializers to a newtype struct that aren't tagged as a newtype struct. + #[inline] + fn visit_newtype_struct(&mut self, + name: &'static str, + visitor: V) -> Result + where V: Visitor, + { + self.visit_tuple_struct(name, 1, visitor) + } + /// This method hints that the `Deserialize` type is expecting a tuple struct. This allows /// deserializers to parse sequences that aren't tagged as sequences. #[inline] @@ -415,6 +426,12 @@ pub trait Visitor { Err(Error::syntax_error()) } + fn visit_newtype_struct(&mut self, _deserializer: &mut D) -> Result + where D: Deserializer, + { + Err(Error::syntax_error()) + } + fn visit_seq(&mut self, _visitor: V) -> Result where V: SeqVisitor, { diff --git a/serde/src/ser/mod.rs b/serde/src/ser/mod.rs index d90ea2d9..909c0a95 100644 --- a/serde/src/ser/mod.rs +++ b/serde/src/ser/mod.rs @@ -125,6 +125,18 @@ pub trait Serializer { self.visit_unit() } + /// The `visit_newtype_struct` allows a tuple struct with a single element, also known as a + /// newtyped value, to be more efficiently serialized than a tuple struct with multiple items. + /// By default it just serializes the value as a tuple struct sequence. + #[inline] + fn visit_newtype_struct(&mut self, + name: &'static str, + value: T) -> Result<(), Self::Error> + where T: Serialize, + { + self.visit_tuple_struct(name, Some(value)) + } + /// The `visit_newtype_variant` allows a variant with a single item to be more efficiently /// serialized than a variant with multiple items. By default it just serializes the value as a /// tuple variant sequence. diff --git a/serde_codegen/src/de.rs b/serde_codegen/src/de.rs index 59ca5a57..58064440 100644 --- a/serde_codegen/src/de.rs +++ b/serde_codegen/src/de.rs @@ -129,15 +129,24 @@ fn deserialize_item_struct( } } - match (named_fields.is_empty(), unnamed_fields == 0) { - (true, true) => { + match (named_fields.is_empty(), unnamed_fields) { + (true, 0) => { deserialize_unit_struct( cx, &builder, item.ident, ) } - (true, false) => { + (true, 1) => { + deserialize_newtype_struct( + cx, + &builder, + item.ident, + impl_generics, + ty, + ) + } + (true, _) => { deserialize_tuple_struct( cx, &builder, @@ -147,7 +156,7 @@ fn deserialize_item_struct( unnamed_fields, ) } - (false, true) => { + (false, 0) => { deserialize_struct( cx, &builder, @@ -157,7 +166,7 @@ fn deserialize_item_struct( struct_def, ) } - (false, false) => { + (false, _) => { cx.bug("struct has named and unnamed fields") } } @@ -278,6 +287,58 @@ fn deserialize_unit_struct( }) } +fn deserialize_newtype_struct( + cx: &ExtCtxt, + builder: &aster::AstBuilder, + type_ident: Ident, + impl_generics: &ast::Generics, + ty: P, +) -> P { + let where_clause = &impl_generics.where_clause; + + let (visitor_item, visitor_ty, visitor_expr, visitor_generics) = + deserialize_visitor( + builder, + impl_generics, + vec![deserializer_ty_param(builder)], + vec![deserializer_ty_arg(builder)], + ); + + let visit_seq_expr = deserialize_seq( + cx, + builder, + builder.path().id(type_ident).build(), + 1, + ); + + let type_name = builder.expr().str(type_ident); + + quote_expr!(cx, { + $visitor_item + + impl $visitor_generics ::serde::de::Visitor for $visitor_ty $where_clause { + type Value = $ty; + + #[inline] + fn visit_newtype_struct(&mut self, deserializer: &mut D) -> Result + where D: ::serde::de::Deserializer, + { + let value = try!(::serde::de::Deserialize::deserialize(deserializer)); + Ok($type_ident(value)) + } + + #[inline] + fn visit_seq<__V>(&mut self, mut visitor: __V) -> ::std::result::Result<$ty, __V::Error> + where __V: ::serde::de::SeqVisitor, + { + $visit_seq_expr + } + } + + deserializer.visit_newtype_struct($type_name, $visitor_expr) + }) +} + fn deserialize_tuple_struct( cx: &ExtCtxt, builder: &aster::AstBuilder, diff --git a/serde_codegen/src/ser.rs b/serde_codegen/src/ser.rs index 6ff126d1..7119faca 100644 --- a/serde_codegen/src/ser.rs +++ b/serde_codegen/src/ser.rs @@ -124,15 +124,22 @@ fn serialize_item_struct( } } - match (named_fields.is_empty(), unnamed_fields == 0) { - (true, true) => { + match (named_fields.is_empty(), unnamed_fields) { + (true, 0) => { serialize_unit_struct( cx, &builder, item.ident, ) } - (true, false) => { + (true, 1) => { + serialize_newtype_struct( + cx, + &builder, + item.ident, + ) + } + (true, _) => { serialize_tuple_struct( cx, &builder, @@ -142,7 +149,7 @@ fn serialize_item_struct( unnamed_fields, ) } - (false, true) => { + (false, 0) => { serialize_struct( cx, &builder, @@ -153,7 +160,7 @@ fn serialize_item_struct( named_fields, ) } - (false, false) => { + (false, _) => { cx.bug("struct has named and unnamed fields") } } @@ -169,6 +176,16 @@ fn serialize_unit_struct( quote_expr!(cx, serializer.visit_unit_struct($type_name)) } +fn serialize_newtype_struct( + cx: &ExtCtxt, + builder: &aster::AstBuilder, + type_ident: Ident +) -> P { + let type_name = builder.expr().str(type_ident); + + quote_expr!(cx, serializer.visit_newtype_struct($type_name, &self.0)) +} + fn serialize_tuple_struct( cx: &ExtCtxt, builder: &aster::AstBuilder, diff --git a/serde_json/src/de.rs b/serde_json/src/de.rs index 3c286ae7..31f406af 100644 --- a/serde_json/src/de.rs +++ b/serde_json/src/de.rs @@ -453,6 +453,15 @@ impl de::Deserializer for Deserializer } } + #[inline] + fn visit_newtype_struct(&mut self, + _name: &str, + mut visitor: V) -> Result + where V: de::Visitor, + { + visitor.visit_newtype_struct(self) + } + #[inline] fn visit_enum(&mut self, _name: &str, diff --git a/serde_json/src/ser.rs b/serde_json/src/ser.rs index 3fe88acd..67674aa5 100644 --- a/serde_json/src/ser.rs +++ b/serde_json/src/ser.rs @@ -158,6 +158,16 @@ impl ser::Serializer for Serializer self.writer.write_all(b"null") } + /// Override `visit_newtype_struct` to serialize newtypes without an object wrapper. + #[inline] + fn visit_newtype_struct(&mut self, + _name: &'static str, + value: T) -> Result<(), Self::Error> + where T: ser::Serialize, + { + value.serialize(self) + } + #[inline] fn visit_unit_variant(&mut self, _name: &str, diff --git a/serde_tests/tests/test_macros.rs b/serde_tests/tests/test_macros.rs index c36e086e..aa64f52e 100644 --- a/serde_tests/tests/test_macros.rs +++ b/serde_tests/tests/test_macros.rs @@ -137,18 +137,17 @@ pub struct GenericStruct { } #[derive(Debug, PartialEq, Serialize, Deserialize)] -pub struct GenericTupleStruct(T); +pub struct GenericNewtypeStruct(T); #[derive(Debug, PartialEq, Serialize, Deserialize)] -pub enum GenericEnumSeq { - Ok(T), - Err(E), -} +pub struct GenericTupleStruct(T, U); #[derive(Debug, PartialEq, Serialize, Deserialize)] -pub enum GenericEnumMap { - Ok { x: T }, - Err { x: E }, +pub enum GenericEnum { + Unit, + Newtype(T), + Seq(T, U), + Map { x: T, y: U }, } #[test] @@ -496,28 +495,42 @@ fn test_lifetimes() { ); } -#[test] -fn test_generic() { - macro_rules! declare_tests { - ($($ty:ty : $value:expr => $str:expr,)+) => { - $({ - let value: $ty = $value; +macro_rules! declare_tests { + ($($name:ident { $($ty:ty : $value:expr => $str:expr,)+ })+) => { + $( + #[test] + fn $name() { + $( + let value: $ty = $value; - let string = serde_json::to_string(&value).unwrap(); - assert_eq!(string, $str); + let string = serde_json::to_string(&value).unwrap(); + assert_eq!(string, $str); - let expected: $ty = serde_json::from_str(&string).unwrap(); - assert_eq!(value, expected); - })+ - } + let expected: $ty = serde_json::from_str(&string).unwrap(); + assert_eq!(value, expected); + )+ + } + )+ + } +} + +declare_tests! { + test_generic_struct { + GenericStruct : GenericStruct { x: 5 } => "{\"x\":5}", + } + test_generic_newtype_struct { + GenericNewtypeStruct : GenericNewtypeStruct(5) => "5", + } + test_generic_tuple_struct { + GenericTupleStruct : GenericTupleStruct(5, 6) => "[5,6]", + } + test_generic_enum_newtype { + GenericEnum : GenericEnum::Newtype(5) => "{\"Newtype\":5}", + } + test_generic_enum_seq { + GenericEnum : GenericEnum::Seq(5, 6) => "{\"Seq\":[5,6]}", + } + test_generic_enum_map { + GenericEnum : GenericEnum::Map { x: 5, y: 6 } => "{\"Map\":{\"x\":5,\"y\":6}}", } - - declare_tests!( - GenericStruct : GenericStruct { x: 5 } => "{\"x\":5}", - GenericTupleStruct : GenericTupleStruct(5) => "[5]", - GenericEnumSeq : GenericEnumSeq::Ok(5) => "{\"Ok\":5}", - GenericEnumSeq : GenericEnumSeq::Err(5) => "{\"Err\":5}", - GenericEnumMap : GenericEnumMap::Ok { x: 5 } => "{\"Ok\":{\"x\":5}}", - GenericEnumMap : GenericEnumMap::Err { x: 5 } => "{\"Err\":{\"x\":5}}", - ); }