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)]); +}