3 stable releases
1.1.1 | Jul 28, 2023 |
---|---|
1.1.0 | Jul 4, 2023 |
1.0.0 | Jul 2, 2023 |
#440 in Encoding
15,352 downloads per month
56KB
1K
SLoC
serde-map-to-array
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 BTreeMap
s and
BTreeMap
-like types is provided.
If feature std
is enabled then support for HashMap
s 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
.
License
serde-map-to-array
is distributed under the terms of both the MIT license and the Apache License (Version 2.0).
See LICENSE-MIT and LICENSE-APACHE for details.
Dependencies
~100–500KB
~11K SLoC