serde_map_to_array/lib.rs
1//! This crate provides unofficial serde helpers to support converting a map to a sequence of named
2//! key-value pairs for [human-readable encoding formats](Serializer::is_human_readable()).
3//!
4//! This allows for a stable schema in the face of a mutable map.
5//!
6//! For example, let's say we have a map containing the values
7//! `[(1, "one"), (2, "two"), (3, "three")]`. Encoded to JSON this is:
8//! ```json
9//! {"1":"one","2":"two","3":"three"}
10//! ```
11//! We cannot specify a schema for this JSON Object though unless the contents of the map are
12//! guaranteed to always contain three entries under the keys `1`, `2` and `3`.
13//!
14//! This crate allows for such a map to be encoded to a JSON Array of Objects, each containing
15//! exactly two elements with static names:
16//! ```json
17//! [{"key":1,"value":"one"},{"key":2,"value":"two"},{"key":3,"value":"three"}]
18//! ```
19//! for which a schema *can* be generated.
20//!
21//! Furthermore, this avoids encoding the key type of the map to a string.
22//!
23//! By default, the key-value pairs will be given the labels "key" and "value", but this can be
24//! modified by providing your own labels via a struct which implements [`KeyValueLabels`].
25//!
26//! Note that for binary (non-human-readable) encoding formats, default serialization and
27//! deserialization is retained.
28//!
29//! # `no_std`
30//!
31//! By default, the crate is `no_std`, but uses `alloc`. In this case, support for `BTreeMap`s and
32//! `BTreeMap`-like types is provided.
33//!
34//! If feature `std` is enabled then support for `HashMap`s and `HashMap`-like types is also
35//! provided, but `no_std` support is disabled.
36//!
37//! # Examples
38//!
39//! ## Using the default field values "key" and "value"
40//! ```
41//! use std::collections::BTreeMap;
42//! use serde::{Deserialize, Serialize};
43//! use serde_map_to_array::BTreeMapToArray;
44//!
45//! #[derive(Default, Serialize, Deserialize)]
46//! struct Data {
47//! #[serde(with = "BTreeMapToArray::<u64, String>")]
48//! inner: BTreeMap<u64, String>,
49//! }
50//!
51//! let mut data = Data::default();
52//! data.inner.insert(1, "one".to_string());
53//! data.inner.insert(2, "two".to_string());
54//!
55//! assert_eq!(
56//! serde_json::to_string(&data).unwrap(),
57//! r#"{"inner":[{"key":1,"value":"one"},{"key":2,"value":"two"}]}"#
58//! );
59//! ```
60//!
61//! ## Using non-default field labels
62//! ```
63//! # #[cfg(feature = "std")] { // make the doctest a no-op when "std" is disabled
64//! use std::collections::HashMap;
65//! use serde::{Deserialize, Serialize};
66//! use serde_map_to_array::{KeyValueLabels, HashMapToArray};
67//!
68//! struct MyKeyValueLabels;
69//!
70//! impl KeyValueLabels for MyKeyValueLabels {
71//! const KEY: &'static str = "id";
72//! const VALUE: &'static str = "name";
73//! }
74//!
75//! #[derive(Default, Serialize, Deserialize)]
76//! struct Data {
77//! #[serde(with = "HashMapToArray::<u64, String, MyKeyValueLabels>")]
78//! inner: HashMap<u64, String>,
79//! }
80//!
81//! let mut data = Data::default();
82//! data.inner.insert(1, "one".to_string());
83//! data.inner.insert(2, "two".to_string());
84//!
85//! // The hashmap orders the entries randomly.
86//! let expected_json = if *data.inner.keys().next().unwrap() == 1 {
87//! r#"{"inner":[{"id":1,"name":"one"},{"id":2,"name":"two"}]}"#
88//! } else {
89//! r#"{"inner":[{"id":2,"name":"two"},{"id":1,"name":"one"}]}"#
90//! };
91//!
92//! assert_eq!(serde_json::to_string(&data).unwrap(), expected_json);
93//! # }
94//! ```
95//!
96//! ## Using a custom `BTreeMap`-like type
97//! ```
98//! use std::collections::{btree_map, BTreeMap};
99//! use serde::{Deserialize, Serialize};
100//! use serde_map_to_array::{BTreeMapToArray, DefaultLabels};
101//!
102//! #[derive(Serialize, Deserialize)]
103//! struct MyMap(BTreeMap<u64, String>);
104//!
105//! /// We need to implement `IntoIterator` to allow serialization.
106//! impl<'a> IntoIterator for &'a MyMap {
107//! type Item = (&'a u64, &'a String);
108//! type IntoIter = btree_map::Iter<'a, u64, String>;
109//!
110//! fn into_iter(self) -> Self::IntoIter {
111//! self.0.iter()
112//! }
113//! }
114//!
115//! /// We need to implement `From<BTreeMap>` to allow deserialization.
116//! impl From<BTreeMap<u64, String>> for MyMap {
117//! fn from(map: BTreeMap<u64, String>) -> Self {
118//! MyMap(map)
119//! }
120//! }
121//!
122//! #[derive(Serialize, Deserialize)]
123//! struct Data {
124//! #[serde(with = "BTreeMapToArray::<u64, String, DefaultLabels, MyMap>")]
125//! inner: MyMap,
126//! }
127//! ```
128//!
129//! ## Using a `HashMap` with a non-standard hasher
130//! ```
131//! # #[cfg(feature = "std")] { // make the doctest a no-op when "std" is disabled
132//! use std::collections::HashMap;
133//! use serde::{Deserialize, Serialize};
134//! use hash_hasher::HashBuildHasher;
135//! use serde_map_to_array::{DefaultLabels, HashMapToArray};
136//!
137//! #[derive(Serialize, Deserialize)]
138//! struct Data {
139//! #[serde(with = "HashMapToArray::<u64, String, DefaultLabels, HashBuildHasher>")]
140//! inner: HashMap<u64, String, HashBuildHasher>,
141//! }
142//! # }
143//! ```
144//!
145//! ## Using a custom `HashMap`-like type
146//! ```
147//! # #[cfg(feature = "std")] { // make the doctest a no-op when "std" is disabled
148//! use std::collections::{hash_map::{self, RandomState}, HashMap};
149//! use serde::{Deserialize, Serialize};
150//! use serde_map_to_array::{DefaultLabels, HashMapToArray};
151//!
152//! #[derive(Serialize, Deserialize)]
153//! struct MyMap(HashMap<u64, String>);
154//!
155//! /// We need to implement `IntoIterator` to allow serialization.
156//! impl<'a> IntoIterator for &'a MyMap {
157//! type Item = (&'a u64, &'a String);
158//! type IntoIter = hash_map::Iter<'a, u64, String>;
159//!
160//! fn into_iter(self) -> Self::IntoIter {
161//! self.0.iter()
162//! }
163//! }
164//!
165//! /// We need to implement `From<HashMap>` to allow deserialization.
166//! impl From<HashMap<u64, String>> for MyMap {
167//! fn from(map: HashMap<u64, String>) -> Self {
168//! MyMap(map)
169//! }
170//! }
171//!
172//! #[derive(Serialize, Deserialize)]
173//! struct Data {
174//! #[serde(with = "HashMapToArray::<u64, String, DefaultLabels, RandomState, MyMap>")]
175//! inner: MyMap,
176//! }
177//! # }
178//! ```
179//!
180//! # JSON Schema Support
181//!
182//! Support for generating JSON schemas via [`schemars`](https://graham.cool/schemars) can be
183//! enabled by setting the feature `json-schema`.
184//!
185//! By default, the schema name of the KeyValue struct will be set to
186//! `"KeyValue_for_{K::schema_name()}_and_{V::schema_name()}"`, and the struct and its key and value
187//! fields will have no descriptions (normally generated from doc comments for the struct and its
188//! fields). Each of these can be modified by providing your own values via a struct which
189//! implements [`KeyValueJsonSchema`].
190
191#![cfg_attr(not(feature = "std"), no_std)]
192#![warn(missing_docs)]
193#![doc(test(attr(deny(warnings))))]
194#![cfg_attr(docsrs, feature(doc_auto_cfg))]
195
196#[doc = include_str!("../README.md")]
197#[cfg(all(doctest, feature = "std"))]
198pub struct ReadmeDoctests;
199
200#[cfg(test)]
201mod tests;
202
203extern crate alloc;
204
205#[cfg(feature = "json-schema")]
206use alloc::borrow::Cow;
207use alloc::collections::BTreeMap;
208use core::{fmt, marker::PhantomData};
209#[cfg(feature = "std")]
210use std::{
211 collections::{hash_map::RandomState, HashMap},
212 hash::{BuildHasher, Hash},
213};
214
215#[cfg(feature = "json-schema")]
216use schemars::{json_schema, JsonSchema, Schema, SchemaGenerator};
217
218use serde::{
219 de::{Error as SerdeError, MapAccess, SeqAccess, Visitor},
220 ser::{SerializeStruct, Serializer},
221 Deserialize, Deserializer, Serialize,
222};
223
224/// A converter between a `BTreeMap` or `BTreeMap`-like type and a sequence of named key-value pairs
225/// for human-readable encoding formats.
226///
227/// See [top-level docs](index.html) for example usage.
228pub struct BTreeMapToArray<K, V, N = DefaultLabels, T = BTreeMap<K, V>>(PhantomData<(K, V, N, T)>);
229
230impl<K, V, N, T> BTreeMapToArray<K, V, N, T> {
231 /// Serializes the given `map` to an array of named key-values if the serializer is
232 /// human-readable. Otherwise serializes the `map` as a map.
233 pub fn serialize<'a, S>(map: &'a T, serializer: S) -> Result<S::Ok, S::Error>
234 where
235 K: 'a + Serialize,
236 V: 'a + Serialize,
237 N: KeyValueLabels,
238 &'a T: IntoIterator<Item = (&'a K, &'a V)> + Serialize,
239 S: Serializer,
240 {
241 serialize::<K, V, N, T, S>(map, serializer)
242 }
243
244 /// Deserializes from a serialized array of named key-values into a map if the serializer is
245 /// human-readable. Otherwise deserializes from a serialized map.
246 pub fn deserialize<'de, D>(deserializer: D) -> Result<T, D::Error>
247 where
248 K: Deserialize<'de> + Ord,
249 V: Deserialize<'de>,
250 N: KeyValueLabels,
251 BTreeMap<K, V>: Into<T>,
252 T: Deserialize<'de>,
253 D: Deserializer<'de>,
254 {
255 let map = if deserializer.is_human_readable() {
256 deserializer.deserialize_seq(BTreeMapToArrayVisitor::<K, V, N>(PhantomData))
257 } else {
258 BTreeMap::<K, V>::deserialize(deserializer)
259 }?;
260 Ok(map.into())
261 }
262}
263
264#[cfg(feature = "json-schema")]
265impl<K, V, N, T> JsonSchema for BTreeMapToArray<K, V, N, T>
266where
267 K: JsonSchema,
268 V: JsonSchema,
269 N: KeyValueJsonSchema,
270{
271 fn schema_name() -> Cow<'static, str> {
272 Vec::<KeyValue<K, V, N>>::schema_name()
273 }
274
275 fn json_schema(generator: &mut SchemaGenerator) -> Schema {
276 Vec::<KeyValue<K, V, N>>::json_schema(generator)
277 }
278}
279
280fn serialize<'a, K, V, N, T, S>(map: &'a T, serializer: S) -> Result<S::Ok, S::Error>
281where
282 K: 'a + Serialize,
283 V: 'a + Serialize,
284 N: KeyValueLabels,
285 &'a T: IntoIterator<Item = (&'a K, &'a V)> + Serialize,
286 S: Serializer,
287{
288 if serializer.is_human_readable() {
289 serializer.collect_seq(map.into_iter().map(|(key, value)| KeyValue {
290 key,
291 value,
292 _phantom: PhantomData::<N>,
293 }))
294 } else {
295 map.serialize(serializer)
296 }
297}
298
299/// A specifier of the labels to be used for the keys and values.
300pub trait KeyValueLabels {
301 /// The label for the keys.
302 const KEY: &'static str;
303 /// The label for the values.
304 const VALUE: &'static str;
305}
306
307#[cfg(feature = "json-schema")]
308/// A specifier of the JSON schema data to be used for the KeyValue struct.
309pub trait KeyValueJsonSchema: KeyValueLabels {
310 /// The value to use for `Schemars::schema_name()` of the KeyValue struct.
311 ///
312 /// If `None`, then a default of `"KeyValue_for_{K::schema_name()}_and_{V::schema_name()}"` is
313 /// applied.
314 const JSON_SCHEMA_KV_NAME: Option<&'static str> = None;
315 /// The description applied to the KeyValue struct.
316 const JSON_SCHEMA_KV_DESCRIPTION: Option<&'static str> = None;
317 /// The description applied to the key of the KeyValue struct.
318 const JSON_SCHEMA_KEY_DESCRIPTION: Option<&'static str> = None;
319 /// The description applied to the value of the KeyValue struct.
320 const JSON_SCHEMA_VALUE_DESCRIPTION: Option<&'static str> = None;
321}
322
323/// A specifier of the default labels to be used for the keys and values.
324///
325/// This struct implements [`KeyValueLabels`] setting key labels to "key" and value labels to
326/// "value".
327pub struct DefaultLabels;
328
329impl KeyValueLabels for DefaultLabels {
330 const KEY: &'static str = "key";
331 const VALUE: &'static str = "value";
332}
333
334#[cfg(feature = "json-schema")]
335impl KeyValueJsonSchema for DefaultLabels {}
336
337/// A helper to support serialization and deserialization for human-readable encoding formats where
338/// the fields `key` and `value` have their labels replaced by the values specified in `N::KEY` and
339/// `N::VALUE`.
340struct KeyValue<K, V, N> {
341 key: K,
342 value: V,
343 _phantom: PhantomData<N>,
344}
345
346impl<K, V, N> Serialize for KeyValue<K, V, N>
347where
348 K: Serialize,
349 V: Serialize,
350 N: KeyValueLabels,
351{
352 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
353 let mut state = serializer.serialize_struct("KeyValue", 2)?;
354 state.serialize_field(N::KEY, &self.key)?;
355 state.serialize_field(N::VALUE, &self.value)?;
356 state.end()
357 }
358}
359
360impl<K, V, N: KeyValueLabels> KeyValue<K, V, N> {
361 const FIELDS: &'static [&'static str] = &[N::KEY, N::VALUE];
362}
363
364impl<'de, K, V, N> Deserialize<'de> for KeyValue<K, V, N>
365where
366 K: Deserialize<'de>,
367 V: Deserialize<'de>,
368 N: KeyValueLabels,
369{
370 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
371 enum Field<N: KeyValueLabels> {
372 Key,
373 Value(PhantomData<N>),
374 }
375
376 impl<'de, N: KeyValueLabels> Deserialize<'de> for Field<N> {
377 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Field<N>, D::Error> {
378 struct FieldVisitor<N>(PhantomData<N>);
379
380 impl<'de, N: KeyValueLabels> Visitor<'de> for FieldVisitor<N> {
381 type Value = Field<N>;
382
383 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
384 write!(formatter, "`{}` or `{}`", N::KEY, N::VALUE)
385 }
386
387 fn visit_str<E: SerdeError>(self, value: &str) -> Result<Field<N>, E> {
388 if value == N::KEY {
389 Ok(Field::Key)
390 } else if value == N::VALUE {
391 Ok(Field::Value(PhantomData))
392 } else {
393 Err(SerdeError::unknown_field(value, &[N::KEY, N::VALUE]))
394 }
395 }
396 }
397
398 deserializer.deserialize_identifier(FieldVisitor(PhantomData))
399 }
400 }
401
402 struct KvVisitor<K, V, N>(PhantomData<(K, V, N)>);
403
404 impl<'de, K, V, N> Visitor<'de> for KvVisitor<K, V, N>
405 where
406 K: Deserialize<'de>,
407 V: Deserialize<'de>,
408 N: KeyValueLabels,
409 {
410 type Value = KeyValue<K, V, N>;
411
412 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
413 formatter.write_str("struct KeyValue")
414 }
415
416 fn visit_seq<SeqA: SeqAccess<'de>>(
417 self,
418 mut seq: SeqA,
419 ) -> Result<KeyValue<K, V, N>, SeqA::Error> {
420 let key = seq
421 .next_element()?
422 .ok_or_else(|| SerdeError::invalid_length(0, &self))?;
423 let value = seq
424 .next_element()?
425 .ok_or_else(|| SerdeError::invalid_length(1, &self))?;
426 Ok(KeyValue {
427 key,
428 value,
429 _phantom: PhantomData,
430 })
431 }
432
433 fn visit_map<MapA: MapAccess<'de>>(
434 self,
435 mut map: MapA,
436 ) -> Result<KeyValue<K, V, N>, MapA::Error> {
437 let mut ret_key = None;
438 let mut ret_value = None;
439 while let Some(key) = map.next_key::<Field<N>>()? {
440 match key {
441 Field::Key => {
442 if ret_key.is_some() {
443 return Err(SerdeError::duplicate_field(N::KEY));
444 }
445 ret_key = Some(map.next_value()?);
446 }
447 Field::Value(_) => {
448 if ret_value.is_some() {
449 return Err(SerdeError::duplicate_field(N::VALUE));
450 }
451 ret_value = Some(map.next_value()?);
452 }
453 }
454 }
455 let key = ret_key.ok_or_else(|| SerdeError::missing_field(N::KEY))?;
456 let value = ret_value.ok_or_else(|| SerdeError::missing_field(N::VALUE))?;
457 Ok(KeyValue {
458 key,
459 value,
460 _phantom: PhantomData,
461 })
462 }
463 }
464
465 deserializer.deserialize_struct("KeyValue", Self::FIELDS, KvVisitor::<_, _, N>(PhantomData))
466 }
467}
468
469#[cfg(feature = "json-schema")]
470impl<K, V, N> JsonSchema for KeyValue<K, V, N>
471where
472 K: JsonSchema,
473 V: JsonSchema,
474 N: KeyValueJsonSchema,
475{
476 fn schema_name() -> Cow<'static, str> {
477 match N::JSON_SCHEMA_KV_NAME {
478 Some(name) => Cow::Borrowed(name),
479 None => Cow::Owned(format!(
480 "KeyValue_for_{}_and_{}",
481 K::schema_name(),
482 V::schema_name()
483 )),
484 }
485 }
486
487 fn json_schema(generator: &mut SchemaGenerator) -> Schema {
488 let apply_description_if_set = |schema: &mut Schema, maybe_description: Option<&str>| {
489 if let Some(description) = maybe_description {
490 schema
491 .ensure_object()
492 .insert("description".into(), description.into());
493 }
494 };
495
496 let mut key_schema = generator.subschema_for::<K>();
497 apply_description_if_set(&mut key_schema, N::JSON_SCHEMA_KEY_DESCRIPTION);
498
499 let mut value_schema = generator.subschema_for::<V>();
500 apply_description_if_set(&mut value_schema, N::JSON_SCHEMA_VALUE_DESCRIPTION);
501
502 let mut schema: Schema = json_schema!({
503 "type": "object",
504 "properties": {
505 N::KEY: key_schema,
506 N::VALUE: value_schema,
507 }
508 });
509 apply_description_if_set(&mut schema, N::JSON_SCHEMA_KV_DESCRIPTION);
510
511 let required = schema
512 .ensure_object()
513 .entry("required")
514 .or_insert(serde_json::Value::Array(Vec::new()))
515 .as_array_mut()
516 .expect("`required` should be an array");
517 required.push(N::KEY.into());
518 required.push(N::VALUE.into());
519
520 schema
521 }
522}
523
524struct BTreeMapToArrayVisitor<K, V, N>(PhantomData<(K, V, N)>);
525
526impl<'de, K, V, N> Visitor<'de> for BTreeMapToArrayVisitor<K, V, N>
527where
528 K: Deserialize<'de> + Ord,
529 V: Deserialize<'de>,
530 N: KeyValueLabels,
531{
532 type Value = BTreeMap<K, V>;
533
534 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
535 formatter.write_str("a BTreeMapToArray")
536 }
537
538 fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
539 let mut map = BTreeMap::new();
540 while let Some(entry) = seq.next_element::<KeyValue<K, V, N>>()? {
541 map.insert(entry.key, entry.value);
542 }
543 Ok(map)
544 }
545}
546
547/// A converter between a `HashMap` or `HashMap`-like type and a sequence of named key-value pairs
548/// for human-readable encoding formats.
549///
550/// See [top-level docs](index.html) for example usage.
551#[cfg(feature = "std")]
552pub struct HashMapToArray<K, V, N = DefaultLabels, U = RandomState, T = HashMap<K, V, U>>(
553 PhantomData<(K, V, N, U, T)>,
554);
555
556#[cfg(feature = "std")]
557impl<K, V, N, U, T> HashMapToArray<K, V, N, U, T> {
558 /// Serializes the given `map` to an array of named key-values if the serializer is
559 /// human-readable. Otherwise serializes the `map` as a map.
560 pub fn serialize<'a, S>(map: &'a T, serializer: S) -> Result<S::Ok, S::Error>
561 where
562 K: 'a + Serialize,
563 V: 'a + Serialize,
564 N: KeyValueLabels,
565 &'a T: IntoIterator<Item = (&'a K, &'a V)> + Serialize,
566 S: Serializer,
567 {
568 serialize::<K, V, N, T, S>(map, serializer)
569 }
570
571 /// Deserializes from a serialized array of named key-values into a map if the serializer is
572 /// human-readable. Otherwise deserializes from a serialized map.
573 pub fn deserialize<'de, D>(deserializer: D) -> Result<T, D::Error>
574 where
575 K: Deserialize<'de> + Eq + Hash,
576 V: Deserialize<'de>,
577 N: KeyValueLabels,
578 U: BuildHasher + Default,
579 HashMap<K, V, U>: Into<T>,
580 T: Deserialize<'de>,
581 D: Deserializer<'de>,
582 {
583 let map = if deserializer.is_human_readable() {
584 deserializer.deserialize_seq(HashMapToArrayVisitor::<K, V, N, U>(PhantomData))
585 } else {
586 HashMap::<K, V, U>::deserialize(deserializer)
587 }?;
588 Ok(map.into())
589 }
590}
591
592#[cfg(feature = "json-schema")]
593impl<K, V, N, U, T> JsonSchema for HashMapToArray<K, V, N, U, T>
594where
595 K: JsonSchema,
596 V: JsonSchema,
597 N: KeyValueJsonSchema,
598{
599 fn schema_name() -> Cow<'static, str> {
600 Vec::<KeyValue<K, V, N>>::schema_name()
601 }
602
603 fn json_schema(generator: &mut SchemaGenerator) -> Schema {
604 Vec::<KeyValue<K, V, N>>::json_schema(generator)
605 }
606}
607
608#[cfg(feature = "std")]
609struct HashMapToArrayVisitor<K, V, N, U>(PhantomData<(K, V, N, U)>);
610
611#[cfg(feature = "std")]
612impl<'de, K, V, N, U> Visitor<'de> for HashMapToArrayVisitor<K, V, N, U>
613where
614 K: Deserialize<'de> + Eq + Hash,
615 V: Deserialize<'de>,
616 N: KeyValueLabels,
617 U: BuildHasher + Default,
618{
619 type Value = HashMap<K, V, U>;
620
621 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
622 formatter.write_str("a HashMapToArray")
623 }
624
625 fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
626 let mut map = HashMap::<K, V, U>::default();
627 while let Some(entry) = seq.next_element::<KeyValue<K, V, N>>()? {
628 map.insert(entry.key, entry.value);
629 }
630 Ok(map)
631 }
632}