Skip to content

Commit

Permalink
Add serde_seq to serialize maps as a sequence
Browse files Browse the repository at this point in the history
Our default serde implementation treats `IndexMap` as a normal map,
which is often a nice representation in serialization formats, but they
might not preserve the order.

This commit adds a `serde_seq` module with `serialize` and `deserialize`
functions, which makes it suitable for serde's field attributes, like
`#[serde(with = "indexmap::serde_seq")]`. This mode treats `IndexMap` as
a sequence of `(key, value)` pairs, which should always preserve order.
  • Loading branch information
cuviper committed Sep 18, 2020
1 parent c39bb07 commit 779eb66
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 10 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ quickcheck = { version = "0.9", default-features = false }
fnv = "1.0"
lazy_static = "1.3"
fxhash = "0.2.1"
serde_derive = "1.0"

[features]
# Serialization with serde 1.0
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ mod equivalent;
mod mutable_keys;
#[cfg(feature = "serde")]
mod serde;
#[cfg(feature = "serde")]
pub mod serde_seq;
mod util;

pub mod map;
Expand Down
112 changes: 112 additions & 0 deletions src/serde_seq.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//! Functions to serialize and deserialize an `IndexMap` as an ordered sequence.
//!
//! The default `serde` implementation serializes `IndexMap` as a normal map,
//! but there is no guarantee that serialization formats will preserve the order
//! of the key-value pairs. This module serializes `IndexMap` as a sequence of
//! `(key, value)` elements instead, in order.
//!
//! This module may be used in a field attribute for derived implementations:
//!
//! ```
//! # use indexmap::IndexMap;
//! # use serde_derive::{Deserialize, Serialize};
//! #[derive(Deserialize, Serialize)]
//! struct Data {
//! #[serde(with = "indexmap::serde_seq")]
//! map: IndexMap<i32, u64>,
//! // ...
//! }
//! ```
//!
//! Requires crate feature `"serde"` or `"serde-1"`

use serde::de::{Deserialize, Deserializer, SeqAccess, Visitor};
use serde::ser::{Serialize, Serializer};

use core::fmt::{self, Formatter};
use core::hash::{BuildHasher, Hash};
use core::marker::PhantomData;

use crate::IndexMap;

/// Serializes an `IndexMap` as an ordered sequence.
///
/// This function may be used in a field attribute for deriving `Serialize`:
///
/// ```
/// # use indexmap::IndexMap;
/// # use serde_derive::Serialize;
/// #[derive(Serialize)]
/// struct Data {
/// #[serde(serialize_with = "indexmap::serde_seq::serialize")]
/// map: IndexMap<i32, u64>,
/// // ...
/// }
/// ```
///
/// Requires crate feature `"serde"` or `"serde-1"`
pub fn serialize<K, V, S, T>(map: &IndexMap<K, V, S>, serializer: T) -> Result<T::Ok, T::Error>
where
K: Serialize + Hash + Eq,
V: Serialize,
S: BuildHasher,
T: Serializer,
{
serializer.collect_seq(map)
}

/// Visitor to deserialize a *sequenced* `IndexMap`
struct SeqVisitor<K, V, S>(PhantomData<(K, V, S)>);

impl<'de, K, V, S> Visitor<'de> for SeqVisitor<K, V, S>
where
K: Deserialize<'de> + Eq + Hash,
V: Deserialize<'de>,
S: Default + BuildHasher,
{
type Value = IndexMap<K, V, S>;

fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
write!(formatter, "a sequenced map")
}

fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let capacity = seq.size_hint().unwrap_or(0);
let mut map = IndexMap::with_capacity_and_hasher(capacity, S::default());

while let Some((key, value)) = seq.next_element()? {
map.insert(key, value);
}

Ok(map)
}
}

/// Deserializes an `IndexMap` from an ordered sequence.
///
/// This function may be used in a field attribute for deriving `Deserialize`:
///
/// ```
/// # use indexmap::IndexMap;
/// # use serde_derive::Deserialize;
/// #[derive(Deserialize)]
/// struct Data {
/// #[serde(deserialize_with = "indexmap::serde_seq::deserialize")]
/// map: IndexMap<i32, u64>,
/// // ...
/// }
/// ```
///
/// Requires crate feature `"serde"` or `"serde-1"`
pub fn deserialize<'de, D, K, V, S>(deserializer: D) -> Result<IndexMap<K, V, S>, D::Error>
where
D: Deserializer<'de>,
K: Deserialize<'de> + Eq + Hash,
V: Deserialize<'de>,
S: Default + BuildHasher,
{
deserializer.deserialize_seq(SeqVisitor(PhantomData))
}
1 change: 1 addition & 0 deletions test-serde/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ edition = "2018"
[dev-dependencies]
fnv = "1.0"
indexmap = { path = "..", features = ["serde-1"] }
serde = { version = "1.0.99", features = ["derive"] }
serde_test = "1.0.99"
60 changes: 50 additions & 10 deletions test-serde/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
#![cfg(test)]

#[macro_use]
extern crate indexmap;
extern crate fnv;
extern crate serde_test;

use fnv::FnvBuildHasher;
use indexmap::{indexmap, indexset, IndexMap, IndexSet};
use serde::{Deserialize, Serialize};
use serde_test::{assert_tokens, Token};

#[test]
fn test_serde() {
fn test_serde_map() {
let map = indexmap! { 1 => 2, 3 => 4 };
assert_tokens(
&map,
Expand Down Expand Up @@ -40,8 +38,8 @@ fn test_serde_set() {
}

#[test]
fn test_serde_fnv_hasher() {
let mut map: ::indexmap::IndexMap<i32, i32, ::fnv::FnvBuildHasher> = Default::default();
fn test_serde_map_fnv_hasher() {
let mut map: IndexMap<i32, i32, FnvBuildHasher> = Default::default();
map.insert(1, 2);
map.insert(3, 4);
assert_tokens(
Expand All @@ -58,8 +56,8 @@ fn test_serde_fnv_hasher() {
}

#[test]
fn test_serde_map_fnv_hasher() {
let mut set: ::indexmap::IndexSet<i32, ::fnv::FnvBuildHasher> = Default::default();
fn test_serde_set_fnv_hasher() {
let mut set: IndexSet<i32, FnvBuildHasher> = Default::default();
set.extend(1..5);
assert_tokens(
&set,
Expand All @@ -73,3 +71,45 @@ fn test_serde_map_fnv_hasher() {
],
);
}

#[test]
fn test_serde_seq_map() {
#[derive(Debug, Deserialize, Serialize)]
#[serde(transparent)]
struct SeqIndexMap {
#[serde(with = "indexmap::serde_seq")]
map: IndexMap<i32, i32>,
}

impl PartialEq for SeqIndexMap {
fn eq(&self, other: &Self) -> bool {
// explicitly compare items in order
self.map.iter().eq(&other.map)
}
}

let map = indexmap! { 1 => 2, 3 => 4, -1 => -2, -3 => -4 };
assert_tokens(
&SeqIndexMap { map },
&[
Token::Seq { len: Some(4) },
Token::Tuple { len: 2 },
Token::I32(1),
Token::I32(2),
Token::TupleEnd,
Token::Tuple { len: 2 },
Token::I32(3),
Token::I32(4),
Token::TupleEnd,
Token::Tuple { len: 2 },
Token::I32(-1),
Token::I32(-2),
Token::TupleEnd,
Token::Tuple { len: 2 },
Token::I32(-3),
Token::I32(-4),
Token::TupleEnd,
Token::SeqEnd,
],
);
}

0 comments on commit 779eb66

Please sign in to comment.