Expand description

This crate provides unofficial serde helpers to support converting a map to a sequence of named key-value pairs for human-readable encoding formats.

This allows for a stable schema in the face of a mutable map.

For example, let’s say we have a map containing the values [(1, "one"), (2, "two"), (3, "three")]. Encoded to JSON this is:

{"1":"one","2":"two","3":"three"}

We cannot specify a schema for this JSON Object though unless the contents of the map are guaranteed to always contain three entries under the keys 1, 2 and 3.

This crate allows for such a map to be encoded to a JSON Array of Objects, each containing exactly two elements with static names:

[{"key":1,"value":"one"},{"key":2,"value":"two"},{"key":3,"value":"three"}]

for which a schema can be generated.

Furthermore, this avoids encoding the key type of the map to a string.

By default, the key-value pairs will be given the labels “key” and “value”, but this can be modified by providing your own labels via a struct which implements KeyValueLabels.

Note that for binary (non-human-readable) encoding formats, default serialization and deserialization is retained.

no_std

By default, the crate is no_std, but uses alloc. In this case, support for BTreeMaps and BTreeMap-like types is provided.

If feature std is enabled then support for HashMaps and HashMap-like types is also provided, but no_std support is disabled.

Examples

Using the default field values “key” and “value”

use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use serde_map_to_array::BTreeMapToArray;

#[derive(Default, Serialize, Deserialize)]
struct Data {
    #[serde(with = "BTreeMapToArray::<u64, String>")]
    inner: BTreeMap<u64, String>,
}

let mut data = Data::default();
data.inner.insert(1, "one".to_string());
data.inner.insert(2, "two".to_string());

assert_eq!(
    serde_json::to_string(&data).unwrap(),
    r#"{"inner":[{"key":1,"value":"one"},{"key":2,"value":"two"}]}"#
);

Using non-default field labels

use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use serde_map_to_array::{KeyValueLabels, HashMapToArray};

struct MyKeyValueLabels;

impl KeyValueLabels for MyKeyValueLabels {
    const KEY: &'static str = "id";
    const VALUE: &'static str = "name";
}

#[derive(Default, Serialize, Deserialize)]
struct Data {
    #[serde(with = "HashMapToArray::<u64, String, MyKeyValueLabels>")]
    inner: HashMap<u64, String>,
}

let mut data = Data::default();
data.inner.insert(1, "one".to_string());
data.inner.insert(2, "two".to_string());

// The hashmap orders the entries randomly.
let expected_json = if *data.inner.keys().next().unwrap() == 1 {
    r#"{"inner":[{"id":1,"name":"one"},{"id":2,"name":"two"}]}"#
} else {
    r#"{"inner":[{"id":2,"name":"two"},{"id":1,"name":"one"}]}"#
};

assert_eq!(serde_json::to_string(&data).unwrap(), expected_json);

Using a custom BTreeMap-like type

use std::collections::{btree_map, BTreeMap};
use serde::{Deserialize, Serialize};
use serde_map_to_array::{BTreeMapToArray, DefaultLabels};

#[derive(Serialize, Deserialize)]
struct MyMap(BTreeMap<u64, String>);

/// We need to implement `IntoIterator` to allow serialization.
impl<'a> IntoIterator for &'a MyMap {
    type Item = (&'a u64, &'a String);
    type IntoIter = btree_map::Iter<'a, u64, String>;

    fn into_iter(self) -> Self::IntoIter {
        self.0.iter()
    }
}

/// We need to implement `From<BTreeMap>` to allow deserialization.
impl From<BTreeMap<u64, String>> for MyMap {
    fn from(map: BTreeMap<u64, String>) -> Self {
        MyMap(map)
    }
}

#[derive(Serialize, Deserialize)]
struct Data {
    #[serde(with = "BTreeMapToArray::<u64, String, DefaultLabels, MyMap>")]
    inner: MyMap,
}

Using a HashMap with a non-standard hasher

use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use hash_hasher::HashBuildHasher;
use serde_map_to_array::{DefaultLabels, HashMapToArray};

#[derive(Serialize, Deserialize)]
struct Data {
    #[serde(with = "HashMapToArray::<u64, String, DefaultLabels, HashBuildHasher>")]
    inner: HashMap<u64, String, HashBuildHasher>,
}

Using a custom HashMap-like type

use std::collections::{hash_map::{self, RandomState}, HashMap};
use serde::{Deserialize, Serialize};
use serde_map_to_array::{DefaultLabels, HashMapToArray};

#[derive(Serialize, Deserialize)]
struct MyMap(HashMap<u64, String>);

/// We need to implement `IntoIterator` to allow serialization.
impl<'a> IntoIterator for &'a MyMap {
    type Item = (&'a u64, &'a String);
    type IntoIter = hash_map::Iter<'a, u64, String>;

    fn into_iter(self) -> Self::IntoIter {
        self.0.iter()
    }
}

/// We need to implement `From<HashMap>` to allow deserialization.
impl From<HashMap<u64, String>> for MyMap {
    fn from(map: HashMap<u64, String>) -> Self {
        MyMap(map)
    }
}

#[derive(Serialize, Deserialize)]
struct Data {
    #[serde(with = "HashMapToArray::<u64, String, DefaultLabels, RandomState, MyMap>")]
    inner: MyMap,
}

JSON Schema Support

Support for generating JSON schemas via schemars can be enabled by setting the feature json-schema.

By default, the schema name of the KeyValue struct will be set to "KeyValue_for_{K::schema_name()}_and_{V::schema_name()}", and the struct and its key and value fields will have no descriptions (normally generated from doc comments for the struct and its fields). Each of these can be modified by providing your own values via a struct which implements KeyValueJsonSchema.

Structs

  • A converter between a BTreeMap or BTreeMap-like type and a sequence of named key-value pairs for human-readable encoding formats.
  • A specifier of the default labels to be used for the keys and values.
  • A converter between a HashMap or HashMap-like type and a sequence of named key-value pairs for human-readable encoding formats.

Traits

  • A specifier of the JSON schema data to be used for the KeyValue struct.
  • A specifier of the labels to be used for the keys and values.