From 1e00af91c13ce8bcede18494e693ef750cef9af0 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Sat, 19 Mar 2016 17:19:45 -0700 Subject: [PATCH] Serde support Serde[1] is a serialization framework that converts Rust data structures to and from a variety of formats: JSON, YAML, TOML, XML, MessagePack, Bincode. This commit adds two impls that allow Serde to serialize and deserialize a LinkedHashMap with any data format supported by Serde. I have put the impls behind a feature to avoid an unwanted dependency for people not using Serde. LinkedHashMap will be valuable to users who need to read a JSON document and preserve the order of keys in a map, or write a JSON document and control the order of map keys in the output. [1]: https://github.com/serde-rs/serde --- .travis.yml | 9 +++--- Cargo.toml | 5 +++ src/lib.rs | 4 +++ src/serde.rs | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++ tests/serde.rs | 44 ++++++++++++++++++++++++++ 5 files changed, 144 insertions(+), 4 deletions(-) create mode 100644 src/serde.rs create mode 100644 tests/serde.rs diff --git a/.travis.yml b/.travis.yml index 4a2adef..78bfc06 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,12 +3,13 @@ sudo: false matrix: include: - rust: beta + env: FEATURES="serde_impl" - rust: nightly - env: FEATURES="--features nightly" + env: FEATURES="serde_impl nightly" script: - - cargo build $FEATURES - - cargo test $FEATURES - - cargo doc --no-deps $FEATURES + - cargo build --features "$FEATURES" + - cargo test --features "$FEATURES" + - cargo doc --no-deps --features "$FEATURES" after_success: | [ $TRAVIS_RUST_VERSION = nightly ] && [ $TRAVIS_BRANCH = master ] && diff --git a/Cargo.toml b/Cargo.toml index afc54c1..8789610 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,3 +17,8 @@ readme = "README.md" [features] nightly = [] +serde_impl = ["serde", "serde_json"] + +[dependencies] +serde = { version = "^0.7", optional = true } +serde_json = { version = "^0.7", optional = true } diff --git a/src/lib.rs b/src/lib.rs index 845db2f..0eed2b9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,6 +31,10 @@ #![cfg_attr(feature = "nightly", feature(hashmap_public_hasher))] #![cfg_attr(all(feature = "nightly", test), feature(test))] +// Optional Serde support +#[cfg(feature = "serde_impl")] +mod serde; + use std::borrow::Borrow; use std::cmp::Ordering; use std::collections::hash_map::{self, HashMap}; diff --git a/src/serde.rs b/src/serde.rs new file mode 100644 index 0000000..ba55935 --- /dev/null +++ b/src/serde.rs @@ -0,0 +1,86 @@ +//! An optional implementation of serialization/deserialization. Reference +//! implementations used: +//! +//! - [Serialize][1]. +//! - [Deserialize][2]. +//! +//! [1]: https://github.com/serde-rs/serde/blob/97856462467db2e90cf368e407c7ebcc726a01a9/serde/src/ser/impls.rs#L601-L611 +//! [2]: https://github.com/serde-rs/serde/blob/97856462467db2e90cf368e407c7ebcc726a01a9/serde/src/de/impls.rs#L694-L746 + +extern crate serde; + +use std::marker::PhantomData; +use std::hash::{BuildHasher, Hash}; + +use super::LinkedHashMap; + +use self::serde::{Serialize, Serializer, Deserialize, Deserializer}; +use self::serde::ser::impls::MapIteratorVisitor; +use self::serde::de::{Visitor, MapVisitor, Error}; + +impl Serialize for LinkedHashMap + where K: Serialize + Eq + Hash, + V: Serialize, + S: BuildHasher +{ + #[inline] + fn serialize(&self, serializer: &mut T) -> Result<(), T::Error> + where T: Serializer, + { + serializer.serialize_map(MapIteratorVisitor::new(self.iter(), Some(self.len()))) + } +} + +/// `serde::de::Visitor` for a linked hash map. +pub struct LinkedHashMapVisitor { + marker: PhantomData>, +} + +impl LinkedHashMapVisitor { + /// Creates a new visitor for a linked hash map. + pub fn new() -> Self { + LinkedHashMapVisitor { + marker: PhantomData, + } + } +} + +impl Visitor for LinkedHashMapVisitor + where K: Deserialize + Eq + Hash, + V: Deserialize, +{ + type Value = LinkedHashMap; + + #[inline] + fn visit_unit(&mut self) -> Result + where E: Error, + { + Ok(LinkedHashMap::new()) + } + + #[inline] + fn visit_map(&mut self, mut visitor: Visitor) -> Result + where Visitor: MapVisitor, + { + let mut values = LinkedHashMap::with_capacity(visitor.size_hint().0); + + while let Some((key, value)) = try!(visitor.visit()) { + values.insert(key, value); + } + + try!(visitor.end()); + + Ok(values) + } +} + +impl Deserialize for LinkedHashMap + where K: Deserialize + Eq + Hash, + V: Deserialize, +{ + fn deserialize(deserializer: &mut D) -> Result, D::Error> + where D: Deserializer, + { + deserializer.deserialize_map(LinkedHashMapVisitor::new()) + } +} diff --git a/tests/serde.rs b/tests/serde.rs new file mode 100644 index 0000000..0b4c6df --- /dev/null +++ b/tests/serde.rs @@ -0,0 +1,44 @@ +#![cfg(feature = "serde_impl")] + +extern crate linked_hash_map; +extern crate serde; +extern crate serde_json; + +use linked_hash_map::LinkedHashMap; + +#[test] +fn test_ser_empty() { + let map = LinkedHashMap::::new(); + let j = serde_json::to_string(&map).unwrap(); + let expected = "{}"; + assert_eq!(j, expected); +} + +#[test] +fn test_ser() { + let mut map = LinkedHashMap::new(); + map.insert("b", 20); + map.insert("a", 10); + map.insert("c", 30); + + let j = serde_json::to_string(&map).unwrap(); + let expected = r#"{"b":20,"a":10,"c":30}"#; + assert_eq!(j, expected); +} + +#[test] +fn test_de_empty() { + let j = "{}"; + let map: LinkedHashMap = serde_json::from_str(j).unwrap(); + assert_eq!(map.len(), 0); +} + +#[test] +fn test_de() { + let j = r#"{"b":20,"a":10,"c":30}"#; + let map: LinkedHashMap = serde_json::from_str(j).unwrap(); + let items: Vec<_> = map.iter().map(|(k, v)| (k.clone(), *v)).collect(); + assert_eq!(items, [("b".to_owned(), 20), + ("a".to_owned(), 10), + ("c".to_owned(), 30)]); +}