diff --git a/CHANGELOG.md b/CHANGELOG.md index fda35677..f43e7dd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +### Unreleased +- Add support for map (de)serialization. + ## [0.4.1] - 2022-05-05 ### Changed diff --git a/src/de/map.rs b/src/de/map.rs index 41f9fe4d..17b88800 100644 --- a/src/de/map.rs +++ b/src/de/map.rs @@ -1,6 +1,5 @@ -use serde::de::{self, Visitor}; - use crate::de::{Deserializer, Error}; +use serde::de::{self, Visitor}; pub struct MapAccess<'a, 'b> { de: &'a mut Deserializer<'b>, @@ -13,6 +12,56 @@ impl<'a, 'b> MapAccess<'a, 'b> { } } +macro_rules! deserialize_signed_key { + ($self:ident, $visitor:ident, $ixx:ident, $visit_ixx:ident) => {{ + let de = $self.de; + match de.parse_whitespace().ok_or(Error::EofWhileParsingValue)? { + b'"' => de.eat_char(), + _ => return Err(Error::InvalidType), + }; + + let result = match de.peek() { + // after rust merged or-patterns feature, these two clause can be merged. + // error[E0658]: or-patterns syntax is experimental + Some(b'0'..=b'9') => super::deserialize_signed!(de, $visitor, $ixx, $visit_ixx), + Some(b'-') => super::deserialize_signed!(de, $visitor, $ixx, $visit_ixx), + _ => return Err(Error::InvalidType), + }; + match de.peek() { + Some(b'"') => { + de.eat_char(); + result + } + _ => Err(Error::InvalidType), + } + }}; +} + +macro_rules! deserialize_unsigned_key { + ($self:ident, $visitor:ident, $ixx:ident, $visit_ixx:ident) => {{ + let de = $self.de; + match de.parse_whitespace().ok_or(Error::EofWhileParsingValue)? { + b'"' => de.eat_char(), + _ => return Err(Error::InvalidType), + }; + + let result = match de.peek() { + // after rust merged or-patterns feature, these two clause can be merged. + // error[E0658]: or-patterns syntax is experimental + Some(b'0'..=b'9') => super::deserialize_unsigned!(de, $visitor, $ixx, $visit_ixx), + Some(b'-') => super::deserialize_unsigned!(de, $visitor, $ixx, $visit_ixx), + _ => return Err(Error::InvalidType), + }; + match de.peek() { + Some(b'"') => { + de.eat_char(); + result + } + _ => Err(Error::InvalidType), + } + }}; +} + impl<'a, 'de> de::MapAccess<'de> for MapAccess<'a, 'de> { type Error = Error; @@ -79,60 +128,75 @@ impl<'de, 'a> de::Deserializer<'de> for MapKey<'a, 'de> { unreachable!() } - fn deserialize_i8(self, _visitor: V) -> Result + fn deserialize_i8(self, visitor: V) -> Result where V: Visitor<'de>, { - unreachable!() + deserialize_signed_key!(self, visitor, i8, visit_i8) } - fn deserialize_i16(self, _visitor: V) -> Result + fn deserialize_i16(self, visitor: V) -> Result where V: Visitor<'de>, { - unreachable!() + deserialize_signed_key!(self, visitor, i16, visit_i16) } - fn deserialize_i32(self, _visitor: V) -> Result + fn deserialize_i32(self, visitor: V) -> Result where V: Visitor<'de>, { - unreachable!() + deserialize_signed_key!(self, visitor, i32, visit_i32) } - fn deserialize_i64(self, _visitor: V) -> Result + fn deserialize_i64(self, visitor: V) -> Result where V: Visitor<'de>, { - unreachable!() + deserialize_signed_key!(self, visitor, i64, visit_i64) } - fn deserialize_u8(self, _visitor: V) -> Result + fn deserialize_i128(self, visitor: V) -> Result where V: Visitor<'de>, { - unreachable!() + // default implementation includes string unparsing + self.de.deserialize_i128(visitor) } - fn deserialize_u16(self, _visitor: V) -> Result + fn deserialize_u8(self, visitor: V) -> Result where V: Visitor<'de>, { - unreachable!() + deserialize_unsigned_key!(self, visitor, u8, visit_u8) } - fn deserialize_u32(self, _visitor: V) -> Result + fn deserialize_u16(self, visitor: V) -> Result where V: Visitor<'de>, { - unreachable!() + deserialize_unsigned_key!(self, visitor, u16, visit_u16) } - fn deserialize_u64(self, _visitor: V) -> Result + fn deserialize_u32(self, visitor: V) -> Result where V: Visitor<'de>, { - unreachable!() + deserialize_unsigned_key!(self, visitor, u32, visit_u32) + } + + fn deserialize_u64(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + deserialize_unsigned_key!(self, visitor, u64, visit_u64) + } + + fn deserialize_u128(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.de.deserialize_u128(visitor) } fn deserialize_f32(self, _visitor: V) -> Result @@ -163,11 +227,11 @@ impl<'de, 'a> de::Deserializer<'de> for MapKey<'a, 'de> { self.de.deserialize_str(visitor) } - fn deserialize_string(self, _visitor: V) -> Result + fn deserialize_string(self, visitor: V) -> Result where V: Visitor<'de>, { - unreachable!() + self.de.deserialize_string(visitor) } fn deserialize_bytes(self, _visitor: V) -> Result diff --git a/src/de/mod.rs b/src/de/mod.rs index b66dc58c..78bf4039 100644 --- a/src/de/mod.rs +++ b/src/de/mod.rs @@ -203,6 +203,7 @@ macro_rules! deserialize_unsigned { } }}; } +pub(crate) use deserialize_unsigned; macro_rules! deserialize_signed { ($self:ident, $visitor:ident, $ixx:ident, $visit_ixx:ident) => {{ @@ -245,6 +246,7 @@ macro_rules! deserialize_signed { } }}; } +pub(crate) use deserialize_signed; impl<'a, 'de> de::Deserializer<'de> for &'a mut Deserializer<'de> { type Error = Error; @@ -576,22 +578,7 @@ impl<'a, 'de> de::Deserializer<'de> for &'a mut Deserializer<'de> { self.deserialize_seq(visitor) } - /// Unsupported. Can’t make an arbitrary-sized map in no-std. Use a struct with a - /// known format, or implement a custom map deserializer / visitor: - /// https://serde.rs/deserialize-map.html - fn deserialize_map(self, _visitor: V) -> Result - where - V: Visitor<'de>, - { - unreachable!() - } - - fn deserialize_struct( - self, - _name: &'static str, - _fields: &'static [&'static str], - visitor: V, - ) -> Result + fn deserialize_map(self, visitor: V) -> Result where V: Visitor<'de>, { @@ -610,6 +597,18 @@ impl<'a, 'de> de::Deserializer<'de> for &'a mut Deserializer<'de> { } } + fn deserialize_struct( + self, + _name: &'static str, + _fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + self.deserialize_map(visitor) + } + fn deserialize_enum( self, _name: &'static str, @@ -1097,6 +1096,101 @@ mod tests { ); } + #[test] + fn numbered_key_maps() { + use std::collections::BTreeMap; + + // u8 + let mut ranking: BTreeMap = BTreeMap::new(); + ranking.insert(1, "Elon".to_string()); + ranking.insert(2, "Bazos".to_string()); + assert_eq!( + from_str::>(r#"{"1": "Elon", "2": "Bazos"}"#).unwrap(), + ranking + ); + + // u16 + let mut ranking: BTreeMap = BTreeMap::new(); + ranking.insert(1, "Elon".to_string()); + ranking.insert(2, "Bazos".to_string()); + assert_eq!( + from_str::>(r#"{"1": "Elon", "2": "Bazos"}"#).unwrap(), + ranking + ); + + // u32 + let mut ranking: BTreeMap = BTreeMap::new(); + ranking.insert(1, "Elon".to_string()); + ranking.insert(2, "Bazos".to_string()); + assert_eq!( + from_str::>(r#"{"1": "Elon", "2": "Bazos"}"#).unwrap(), + ranking + ); + + // u64 + let mut ranking: BTreeMap = BTreeMap::new(); + ranking.insert(1, "Elon".to_string()); + ranking.insert(2, "Bazos".to_string()); + assert_eq!( + from_str::>(r#"{"1": "Elon", "2": "Bazos"}"#).unwrap(), + ranking + ); + + // u128 + let mut ranking: BTreeMap = BTreeMap::new(); + ranking.insert(1, "Elon".to_string()); + ranking.insert(2, "Bazos".to_string()); + assert_eq!( + from_str::>(r#"{"1": "Elon", "2": "Bazos"}"#).unwrap(), + ranking + ); + + // i8 + let mut ranking: BTreeMap = BTreeMap::new(); + ranking.insert(1, "Elon".to_string()); + ranking.insert(2, "Bazos".to_string()); + assert_eq!( + from_str::>(r#"{"1": "Elon", "2": "Bazos"}"#).unwrap(), + ranking + ); + + // i16 + let mut ranking: BTreeMap = BTreeMap::new(); + ranking.insert(1, "Elon".to_string()); + ranking.insert(2, "Bazos".to_string()); + assert_eq!( + from_str::>(r#"{"1": "Elon", "2": "Bazos"}"#).unwrap(), + ranking + ); + + // i32 + let mut ranking: BTreeMap = BTreeMap::new(); + ranking.insert(1, "Elon".to_string()); + ranking.insert(2, "Bazos".to_string()); + assert_eq!( + from_str::>(r#"{"1": "Elon", "2": "Bazos"}"#).unwrap(), + ranking + ); + + // i64 + let mut ranking: BTreeMap = BTreeMap::new(); + ranking.insert(1, "Elon".to_string()); + ranking.insert(2, "Bazos".to_string()); + assert_eq!( + from_str::>(r#"{"1": "Elon", "2": "Bazos"}"#).unwrap(), + ranking + ); + + // i128 + let mut ranking: BTreeMap = BTreeMap::new(); + ranking.insert(1, "Elon".to_string()); + ranking.insert(2, "Bazos".to_string()); + assert_eq!( + from_str::>(r#"{"1": "Elon", "2": "Bazos"}"#).unwrap(), + ranking + ); + } + #[test] fn deserialize_optional_vector() { #[derive(Debug, Deserialize, PartialEq)] diff --git a/src/lib.rs b/src/lib.rs index 6a82e060..4ce4e51e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -65,6 +65,8 @@ pub use self::ser::{to_string, to_vec}; #[cfg(test)] mod test { + use std::collections::BTreeMap; + use super::*; use serde_derive::{Deserialize, Serialize}; @@ -95,6 +97,7 @@ mod test { published: bool, comments: Vec, stats: Stats, + balances: BTreeMap, } #[test] @@ -107,7 +110,10 @@ mod test { published: false, comments: vec![], stats: Stats { views: 0, score: 0 }, + balances: BTreeMap::new(), }; + let mut balances: BTreeMap = BTreeMap::new(); + balances.insert("chareen".into(), 347); let max = Item { model: Model::Post { category: "fun".to_string(), @@ -122,6 +128,7 @@ mod test { views: std::u64::MAX, score: std::i64::MIN, }, + balances, }; // binary @@ -172,6 +179,9 @@ mod test { author: Address("no-reply@domain.com".to_owned()), }); + let mut balances: BTreeMap = BTreeMap::new(); + balances.insert("chareen".into(), 347); + let item = ModelOrItem::Item(Item { model: Model::Comment, title: "Title".to_owned(), @@ -183,6 +193,7 @@ mod test { views: 110, score: 12, }, + balances, }); assert_eq!( diff --git a/src/ser/map.rs b/src/ser/map.rs new file mode 100644 index 00000000..c82c2ee3 --- /dev/null +++ b/src/ser/map.rs @@ -0,0 +1,258 @@ +use std::fmt; + +use serde::{ser, Serialize}; + +use crate::ser::{Error, Result, Serializer}; + +use super::{seq::SerializeSeq, struct_::SerializeStruct, Unreachable}; + +pub struct SerializeMap<'a> { + ser: &'a mut Serializer, + first: bool, +} + +impl<'a> SerializeMap<'a> { + pub(crate) fn new(ser: &'a mut Serializer) -> Self { + SerializeMap { ser, first: true } + } +} + +impl<'a> ser::SerializeMap for SerializeMap<'a> { + type Ok = (); + type Error = Error; + + fn end(self) -> Result { + self.ser.buf.push(b'}'); + Ok(()) + } + + fn serialize_key(&mut self, key: &T) -> Result<()> + where + T: ser::Serialize, + { + if !self.first { + self.ser.buf.push(b','); + } + self.first = false; + // Use key serializer to unsure key type validity. + key.serialize(MapKeySerializer { ser: self.ser })?; + self.ser.buf.extend_from_slice(b":"); + Ok(()) + } + + fn serialize_value(&mut self, value: &T) -> Result<()> + where + T: ser::Serialize, + { + value.serialize(&mut *self.ser)?; + Ok(()) + } +} + +/// Wrapper around Serializer that only allows serialization of valid JSON key types (strings). +struct MapKeySerializer<'a> { + ser: &'a mut Serializer, +} + +pub(crate) fn key_must_be_a_string() -> Error { + Error::Custom("JSON object key is required to be a string type.".to_string()) +} + +macro_rules! serialize_unsigned_key { + ($self:ident, $N:expr, $v:expr) => {{ + let ser = $self.ser; + ser.buf.push(b'"'); + let res: Result = super::serialize_unsigned!(ser, $N, $v); + res?; + ser.buf.push(b'"'); + Ok(()) + }}; +} + +macro_rules! serialize_signed_key { + ($self:ident, $N:expr, $v:expr, $ixx:ident, $uxx:ident) => {{ + let ser = $self.ser; + ser.buf.push(b'"'); + let res: Result = super::serialize_signed!(ser, $N, $v, $ixx, $uxx); + res?; + ser.buf.push(b'"'); + Ok(()) + }}; +} + +impl<'a> ser::Serializer for MapKeySerializer<'a> { + type Ok = (); + type Error = Error; + type SerializeSeq = SerializeSeq<'a>; + type SerializeTuple = SerializeSeq<'a>; + type SerializeTupleStruct = Unreachable; + type SerializeTupleVariant = SerializeSeq<'a>; + type SerializeMap = SerializeMap<'a>; + type SerializeStruct = SerializeStruct<'a>; + type SerializeStructVariant = SerializeStruct<'a>; + + fn serialize_bool(self, _value: bool) -> Result<()> { + Err(key_must_be_a_string()) + } + #[inline] + fn serialize_str(self, value: &str) -> Result<()> { + self.ser.serialize_str(value) + } + + #[inline] + fn serialize_unit_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + ) -> Result<()> { + self.ser.serialize_str(variant) + } + + #[inline] + fn serialize_newtype_struct(self, _name: &'static str, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + value.serialize(self) + } + + fn serialize_i8(self, value: i8) -> Result<()> { + serialize_signed_key!(self, 4, value, i8, u8) + } + + fn serialize_i16(self, value: i16) -> Result<()> { + serialize_signed_key!(self, 6, value, i16, u16) + } + + fn serialize_i32(self, value: i32) -> Result<()> { + serialize_signed_key!(self, 11, value, i32, u32) + } + + fn serialize_i64(self, value: i64) -> Result<()> { + serialize_signed_key!(self, 20, value, i64, u64) + } + + fn serialize_i128(self, value: i128) -> Result<()> { + serialize_signed_key!(self, 40, value, i128, u128) + } + + fn serialize_u8(self, value: u8) -> Result<()> { + serialize_unsigned_key!(self, 3, value) + } + + fn serialize_u16(self, value: u16) -> Result<()> { + serialize_unsigned_key!(self, 5, value) + } + + fn serialize_u32(self, value: u32) -> Result<()> { + serialize_unsigned_key!(self, 10, value) + } + + fn serialize_u64(self, value: u64) -> Result<()> { + serialize_unsigned_key!(self, 20, value) + } + + fn serialize_u128(self, value: u128) -> Result<()> { + serialize_unsigned_key!(self, 39, value) + } + + fn serialize_f32(self, _value: f32) -> Result<()> { + Err(key_must_be_a_string()) + } + + fn serialize_f64(self, _value: f64) -> Result<()> { + Err(key_must_be_a_string()) + } + + fn serialize_char(self, value: char) -> Result<()> { + self.ser.serialize_str(&value.to_string()) + } + + fn serialize_bytes(self, _value: &[u8]) -> Result<()> { + Err(key_must_be_a_string()) + } + + fn serialize_unit(self) -> Result<()> { + Err(key_must_be_a_string()) + } + + fn serialize_unit_struct(self, _name: &'static str) -> Result<()> { + Err(key_must_be_a_string()) + } + + fn serialize_newtype_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _value: &T, + ) -> Result<()> + where + T: ?Sized + Serialize, + { + Err(key_must_be_a_string()) + } + + fn serialize_none(self) -> Result<()> { + Err(key_must_be_a_string()) + } + + fn serialize_some(self, _value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + Err(key_must_be_a_string()) + } + + fn serialize_seq(self, _len: Option) -> Result { + Err(key_must_be_a_string()) + } + + fn serialize_tuple(self, _len: usize) -> Result { + Err(key_must_be_a_string()) + } + + fn serialize_tuple_struct( + self, + _name: &'static str, + _len: usize, + ) -> Result { + Err(key_must_be_a_string()) + } + + fn serialize_tuple_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize, + ) -> Result { + Err(key_must_be_a_string()) + } + + fn serialize_map(self, _len: Option) -> Result { + Err(key_must_be_a_string()) + } + + fn serialize_struct(self, _name: &'static str, _len: usize) -> Result { + Err(key_must_be_a_string()) + } + + fn serialize_struct_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize, + ) -> Result { + Err(key_must_be_a_string()) + } + + fn collect_str(self, _value: &T) -> Result<()> + where + T: ?Sized + fmt::Display, + { + unreachable!() + } +} diff --git a/src/ser/mod.rs b/src/ser/mod.rs index b3d8a24d..98a280b5 100644 --- a/src/ser/mod.rs +++ b/src/ser/mod.rs @@ -6,9 +6,11 @@ use serde::ser; use std::vec::Vec; +use self::map::SerializeMap; use self::seq::SerializeSeq; use self::struct_::SerializeStruct; +mod map; mod seq; mod struct_; @@ -97,6 +99,8 @@ macro_rules! serialize_unsigned { Ok(()) }}; } +// Export for use in map +pub(crate) use serialize_unsigned; macro_rules! serialize_signed { ($self:ident, $N:expr, $v:expr, $ixx:ident, $uxx:ident) => {{ @@ -131,6 +135,8 @@ macro_rules! serialize_signed { Ok(()) }}; } +// Export for use in map +pub(crate) use serialize_signed; /// Upper-case hex for value in 0..16, encoded as ASCII bytes fn hex_4bit(c: u8) -> u8 { @@ -153,7 +159,7 @@ impl<'a> ser::Serializer for &'a mut Serializer { type SerializeTuple = SerializeSeq<'a>; type SerializeTupleStruct = Unreachable; type SerializeTupleVariant = SerializeSeq<'a>; - type SerializeMap = Unreachable; + type SerializeMap = SerializeMap<'a>; type SerializeStruct = SerializeStruct<'a>; type SerializeStructVariant = SerializeStruct<'a>; @@ -400,7 +406,8 @@ impl<'a> ser::Serializer for &'a mut Serializer { } fn serialize_map(self, _len: Option) -> Result { - unreachable!() + self.buf.push(b'{'); + Ok(SerializeMap::new(self)) } fn serialize_struct(self, _name: &'static str, _len: usize) -> Result { @@ -530,6 +537,7 @@ impl ser::SerializeStructVariant for Unreachable { #[cfg(test)] mod tests { + use super::to_string; use serde_derive::Serialize; @@ -979,10 +987,238 @@ mod tests { ); } - use serde_derive::Deserialize; + #[test] + fn btree_map() { + use std::collections::BTreeMap; + + // empty map + assert_eq!(to_string(&BTreeMap::<(), ()>::new()).unwrap(), r#"{}"#); + + // One element with unit type + let mut map = BTreeMap::<&str, ()>::new(); + map.insert("set_element", ()); + assert_eq!(to_string(&map).unwrap(), r#"{"set_element":null}"#); + + let mut two_values = BTreeMap::new(); + two_values.insert("my_name", "joseph"); + two_values.insert("her_name", "aline"); + assert_eq!( + to_string(&two_values).unwrap(), + r#"{"her_name":"aline","my_name":"joseph"}"# + ); + + let mut nested_map = BTreeMap::new(); + nested_map.insert("two_entries", two_values.clone()); + + two_values.remove("my_name"); + nested_map.insert("one_entry", two_values); + assert_eq!( + to_string(&nested_map).unwrap(), + r#"{"one_entry":{"her_name":"aline"},"two_entries":{"her_name":"aline","my_name":"joseph"}}"# + ); + } + + #[test] + fn hash_map() { + use std::collections::HashMap; + + // empty map + assert_eq!(to_string(&HashMap::<(), ()>::new()).unwrap(), r#"{}"#); + + // One element + let mut map = HashMap::new(); + map.insert("my_age", 28); + assert_eq!(to_string(&map).unwrap(), r#"{"my_age":28}"#); + + #[derive(Debug, Serialize, PartialEq, Eq, Hash)] + pub struct NewType(String); + + // New type wrappers around String types work as keys + let mut map = HashMap::new(); + map.insert(NewType(String::from("my_age")), 44); + assert_eq!(to_string(&map).unwrap(), r#"{"my_age":44}"#); + + #[derive(Debug, Serialize, PartialEq, Eq, Hash)] + #[serde(rename_all = "lowercase")] + pub enum MyResult { + Err, + } + + // Unit variants are also valid keys + let mut map = HashMap::new(); + map.insert(MyResult::Err, 404); + assert_eq!(to_string(&map).unwrap(), r#"{"err":404}"#); + + // HashMap does not have deterministic iteration order (except in the Wasm target). + // So the two element map is serialized as one of two options. + let mut two_values = HashMap::new(); + two_values.insert("my_name", "joseph"); + two_values.insert("her_name", "aline"); + let serialized = to_string(&two_values).unwrap(); + assert!( + serialized == r#"{"her_name":"aline","my_name":"joseph"}"# + || serialized == r#"{"my_name":"joseph","her_name":"aline"}"# + ); + } + + #[test] + fn map_serialization_matches_json_serde() { + use std::collections::BTreeMap; + + fn ser_actual(value: &T) -> String { + to_string(value).unwrap() + } + + fn ser_expected(value: &T) -> String { + serde_json::to_string(value).unwrap() + } + + let map = BTreeMap::<(), ()>::new(); + assert_eq!(ser_actual(&map), ser_expected(&map)); + + let mut two_values = BTreeMap::new(); + two_values.insert("my_name", "joseph"); + two_values.insert("her_name", "aline"); + assert_eq!(ser_actual(&two_values), ser_expected(&two_values)); + + let mut nested_map = BTreeMap::new(); + nested_map.insert("two_entries", two_values.clone()); + two_values.remove("my_name"); + nested_map.insert("one_entry", two_values); + assert_eq!(ser_actual(&nested_map), ser_expected(&nested_map)); + + // One element with unit type + let mut map = BTreeMap::<&str, ()>::new(); + map.insert("set_element", ()); + assert_eq!(ser_actual(&map), ser_expected(&map)); + + // numeric keys + let mut map = BTreeMap::new(); + map.insert(10i8, "my_age"); + assert_eq!(ser_actual(&map), ser_expected(&map)); + + // numeric values + let mut scores = BTreeMap::new(); + scores.insert("player A", 1234212); + assert_eq!(ser_actual(&scores), ser_expected(&scores)); + } + + #[test] + fn number_key() { + use std::collections::HashMap; + + // i8 key + let mut map = HashMap::new(); + map.insert(10i8, "my_age"); + assert_eq!(to_string(&map).unwrap(), r#"{"10":"my_age"}"#); + + // i16 key + let mut map = HashMap::new(); + map.insert(10i16, "my_age"); + assert_eq!(to_string(&map).unwrap(), r#"{"10":"my_age"}"#); + + // i32 key + let mut map = HashMap::new(); + map.insert(10i32, "my_age"); + assert_eq!(to_string(&map).unwrap(), r#"{"10":"my_age"}"#); + + // i64 key + let mut map = HashMap::new(); + map.insert(10i64, "my_age"); + assert_eq!(to_string(&map).unwrap(), r#"{"10":"my_age"}"#); + + // i128 key + let mut map = HashMap::new(); + map.insert(10i128, "my_age"); + assert_eq!(to_string(&map).unwrap(), r#"{"10":"my_age"}"#); + + // u8 key + let mut map = HashMap::new(); + map.insert(10u8, "my_age"); + assert_eq!(to_string(&map).unwrap(), r#"{"10":"my_age"}"#); + + // u16 key + let mut map = HashMap::new(); + map.insert(10u16, "my_age"); + assert_eq!(to_string(&map).unwrap(), r#"{"10":"my_age"}"#); + + // u32 key + let mut map = HashMap::new(); + map.insert(10u32, "my_age"); + assert_eq!(to_string(&map).unwrap(), r#"{"10":"my_age"}"#); + + // u64 key + let mut map = HashMap::new(); + map.insert(10u64, "my_age"); + assert_eq!(to_string(&map).unwrap(), r#"{"10":"my_age"}"#); + + // u128 key + let mut map = HashMap::new(); + map.insert(10u128, "my_age"); + assert_eq!(to_string(&map).unwrap(), r#"{"10":"my_age"}"#); + } + + #[test] + fn invalid_json_key() { + use crate::ser::map::key_must_be_a_string; + use std::collections::HashMap; + + #[derive(Debug, Serialize, PartialEq, Eq, Hash)] + #[serde(rename_all = "lowercase")] + pub enum MyResult { + Unit(()), + Ok(Response), + } + #[derive(Debug, Serialize, PartialEq, Eq, Hash)] + pub struct Response { + pub log: Option, + pub count: i64, + pub list: Vec, + } + + // unit enum + let mut map = HashMap::new(); + map.insert(MyResult::Unit(()), "my_age"); + assert_eq!( + to_string(&map).unwrap_err().to_string(), + key_must_be_a_string().to_string() + ); + + // struct enum + let mut map = HashMap::new(); + map.insert( + MyResult::Ok(Response { + log: None, + count: 1, + list: vec![6], + }), + "my_age", + ); + assert_eq!( + to_string(&map).unwrap_err().to_string(), + key_must_be_a_string().to_string() + ); + + // Struct + let mut map = HashMap::new(); + map.insert( + Response { + log: None, + count: 1, + list: vec![6], + }, + "my_age", + ); + assert_eq!( + to_string(&map).unwrap_err().to_string(), + key_must_be_a_string().to_string() + ); + } #[test] fn serialize_embedded_enum() { + use serde_derive::Deserialize; + #[derive(Debug, Deserialize, Serialize, PartialEq)] #[serde(rename_all = "lowercase")] pub enum MyResult { diff --git a/src/ser/struct_.rs b/src/ser/struct_.rs index 3898db82..502d0346 100644 --- a/src/ser/struct_.rs +++ b/src/ser/struct_.rs @@ -3,13 +3,13 @@ use serde::ser; use crate::ser::{Error, Result, Serializer}; pub struct SerializeStruct<'a> { - de: &'a mut Serializer, + ser: &'a mut Serializer, first: bool, } impl<'a> SerializeStruct<'a> { - pub(crate) fn new(de: &'a mut Serializer) -> Self { - SerializeStruct { de, first: true } + pub(crate) fn new(ser: &'a mut Serializer) -> Self { + SerializeStruct { ser, first: true } } } @@ -23,21 +23,21 @@ impl<'a> ser::SerializeStruct for SerializeStruct<'a> { { // XXX if `value` is `None` we not produce any output for this field if !self.first { - self.de.buf.push(b','); + self.ser.buf.push(b','); } self.first = false; - self.de.buf.push(b'"'); - self.de.buf.extend_from_slice(key.as_bytes()); - self.de.buf.extend_from_slice(b"\":"); + self.ser.buf.push(b'"'); + self.ser.buf.extend_from_slice(key.as_bytes()); + self.ser.buf.extend_from_slice(b"\":"); - value.serialize(&mut *self.de)?; + value.serialize(&mut *self.ser)?; Ok(()) } fn end(self) -> Result { - self.de.buf.push(b'}'); + self.ser.buf.push(b'}'); Ok(()) } } @@ -52,24 +52,24 @@ impl<'a> ser::SerializeStructVariant for SerializeStruct<'a> { { // XXX if `value` is `None` we not produce any output for this field if !self.first { - self.de.buf.push(b','); + self.ser.buf.push(b','); } self.first = false; - self.de.buf.push(b'"'); - self.de.buf.extend_from_slice(key.as_bytes()); - self.de.buf.extend_from_slice(b"\":"); + self.ser.buf.push(b'"'); + self.ser.buf.extend_from_slice(key.as_bytes()); + self.ser.buf.extend_from_slice(b"\":"); - value.serialize(&mut *self.de)?; + value.serialize(&mut *self.ser)?; Ok(()) } fn end(self) -> Result { // close struct - self.de.buf.push(b'}'); + self.ser.buf.push(b'}'); // close surrounding enum - self.de.buf.push(b'}'); + self.ser.buf.push(b'}'); Ok(()) } }