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
205use alloc::collections::BTreeMap;
206use core::{fmt, marker::PhantomData};
207#[cfg(feature = "std")]
208use std::{
209    collections::{hash_map::RandomState, HashMap},
210    hash::{BuildHasher, Hash},
211};
212
213#[cfg(feature = "json-schema")]
214use schemars::{
215    gen::SchemaGenerator,
216    schema::{InstanceType, Schema, SchemaObject},
217    JsonSchema,
218};
219
220use serde::{
221    de::{Error as SerdeError, MapAccess, SeqAccess, Visitor},
222    ser::{SerializeStruct, Serializer},
223    Deserialize, Deserializer, Serialize,
224};
225
226/// A converter between a `BTreeMap` or `BTreeMap`-like type and a sequence of named key-value pairs
227/// for human-readable encoding formats.
228///
229/// See [top-level docs](index.html) for example usage.
230pub struct BTreeMapToArray<K, V, N = DefaultLabels, T = BTreeMap<K, V>>(PhantomData<(K, V, N, T)>);
231
232impl<K, V, N, T> BTreeMapToArray<K, V, N, T> {
233    /// Serializes the given `map` to an array of named key-values if the serializer is
234    /// human-readable.  Otherwise serializes the `map` as a map.
235    pub fn serialize<'a, S>(map: &'a T, serializer: S) -> Result<S::Ok, S::Error>
236    where
237        K: 'a + Serialize,
238        V: 'a + Serialize,
239        N: KeyValueLabels,
240        &'a T: IntoIterator<Item = (&'a K, &'a V)> + Serialize,
241        S: Serializer,
242    {
243        serialize::<K, V, N, T, S>(map, serializer)
244    }
245
246    /// Deserializes from a serialized array of named key-values into a map if the serializer is
247    /// human-readable.  Otherwise deserializes from a serialized map.
248    pub fn deserialize<'de, D>(deserializer: D) -> Result<T, D::Error>
249    where
250        K: Deserialize<'de> + Ord,
251        V: Deserialize<'de>,
252        N: KeyValueLabels,
253        BTreeMap<K, V>: Into<T>,
254        T: Deserialize<'de>,
255        D: Deserializer<'de>,
256    {
257        let map = if deserializer.is_human_readable() {
258            deserializer.deserialize_seq(BTreeMapToArrayVisitor::<K, V, N>(PhantomData))
259        } else {
260            BTreeMap::<K, V>::deserialize(deserializer)
261        }?;
262        Ok(map.into())
263    }
264}
265
266#[cfg(feature = "json-schema")]
267impl<K, V, N, T> JsonSchema for BTreeMapToArray<K, V, N, T>
268where
269    K: JsonSchema,
270    V: JsonSchema,
271    N: KeyValueJsonSchema,
272{
273    fn schema_name() -> String {
274        Vec::<KeyValue<K, V, N>>::schema_name()
275    }
276
277    fn json_schema(gen: &mut SchemaGenerator) -> Schema {
278        Vec::<KeyValue<K, V, N>>::json_schema(gen)
279    }
280}
281
282fn serialize<'a, K, V, N, T, S>(map: &'a T, serializer: S) -> Result<S::Ok, S::Error>
283where
284    K: 'a + Serialize,
285    V: 'a + Serialize,
286    N: KeyValueLabels,
287    &'a T: IntoIterator<Item = (&'a K, &'a V)> + Serialize,
288    S: Serializer,
289{
290    if serializer.is_human_readable() {
291        serializer.collect_seq(map.into_iter().map(|(key, value)| KeyValue {
292            key,
293            value,
294            _phantom: PhantomData::<N>,
295        }))
296    } else {
297        map.serialize(serializer)
298    }
299}
300
301/// A specifier of the labels to be used for the keys and values.
302pub trait KeyValueLabels {
303    /// The label for the keys.
304    const KEY: &'static str;
305    /// The label for the values.
306    const VALUE: &'static str;
307}
308
309#[cfg(feature = "json-schema")]
310/// A specifier of the JSON schema data to be used for the KeyValue struct.
311pub trait KeyValueJsonSchema: KeyValueLabels {
312    /// The value to use for `Schemars::schema_name()` of the KeyValue struct.
313    ///
314    /// If `None`, then a default of `"KeyValue_for_{K::schema_name()}_and_{V::schema_name()}"` is
315    /// applied.
316    const JSON_SCHEMA_KV_NAME: Option<&'static str> = None;
317    /// The description applied to the KeyValue struct.
318    const JSON_SCHEMA_KV_DESCRIPTION: Option<&'static str> = None;
319    /// The description applied to the key of the KeyValue struct.
320    const JSON_SCHEMA_KEY_DESCRIPTION: Option<&'static str> = None;
321    /// The description applied to the value of the KeyValue struct.
322    const JSON_SCHEMA_VALUE_DESCRIPTION: Option<&'static str> = None;
323}
324
325/// A specifier of the default labels to be used for the keys and values.
326///
327/// This struct implements [`KeyValueLabels`] setting key labels to "key" and value labels to
328/// "value".
329pub struct DefaultLabels;
330
331impl KeyValueLabels for DefaultLabels {
332    const KEY: &'static str = "key";
333    const VALUE: &'static str = "value";
334}
335
336#[cfg(feature = "json-schema")]
337impl KeyValueJsonSchema for DefaultLabels {}
338
339/// A helper to support serialization and deserialization for human-readable encoding formats where
340/// the fields `key` and `value` have their labels replaced by the values specified in `N::KEY` and
341/// `N::VALUE`.
342struct KeyValue<K, V, N> {
343    key: K,
344    value: V,
345    _phantom: PhantomData<N>,
346}
347
348impl<K, V, N> Serialize for KeyValue<K, V, N>
349where
350    K: Serialize,
351    V: Serialize,
352    N: KeyValueLabels,
353{
354    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
355        let mut state = serializer.serialize_struct("KeyValue", 2)?;
356        state.serialize_field(N::KEY, &self.key)?;
357        state.serialize_field(N::VALUE, &self.value)?;
358        state.end()
359    }
360}
361
362impl<K, V, N: KeyValueLabels> KeyValue<K, V, N> {
363    const FIELDS: &'static [&'static str] = &[N::KEY, N::VALUE];
364}
365
366impl<'de, K, V, N> Deserialize<'de> for KeyValue<K, V, N>
367where
368    K: Deserialize<'de>,
369    V: Deserialize<'de>,
370    N: KeyValueLabels,
371{
372    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
373        enum Field<N: KeyValueLabels> {
374            Key,
375            Value(PhantomData<N>),
376        }
377
378        impl<'de, N: KeyValueLabels> Deserialize<'de> for Field<N> {
379            fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Field<N>, D::Error> {
380                struct FieldVisitor<N>(PhantomData<N>);
381
382                impl<'de, N: KeyValueLabels> Visitor<'de> for FieldVisitor<N> {
383                    type Value = Field<N>;
384
385                    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
386                        write!(formatter, "`{}` or `{}`", N::KEY, N::VALUE)
387                    }
388
389                    fn visit_str<E: SerdeError>(self, value: &str) -> Result<Field<N>, E> {
390                        if value == N::KEY {
391                            Ok(Field::Key)
392                        } else if value == N::VALUE {
393                            Ok(Field::Value(PhantomData))
394                        } else {
395                            Err(SerdeError::unknown_field(value, &[N::KEY, N::VALUE]))
396                        }
397                    }
398                }
399
400                deserializer.deserialize_identifier(FieldVisitor(PhantomData))
401            }
402        }
403
404        struct KvVisitor<K, V, N>(PhantomData<(K, V, N)>);
405
406        impl<'de, K, V, N> Visitor<'de> for KvVisitor<K, V, N>
407        where
408            K: Deserialize<'de>,
409            V: Deserialize<'de>,
410            N: KeyValueLabels,
411        {
412            type Value = KeyValue<K, V, N>;
413
414            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
415                formatter.write_str("struct KeyValue")
416            }
417
418            fn visit_seq<SeqA: SeqAccess<'de>>(
419                self,
420                mut seq: SeqA,
421            ) -> Result<KeyValue<K, V, N>, SeqA::Error> {
422                let key = seq
423                    .next_element()?
424                    .ok_or_else(|| SerdeError::invalid_length(0, &self))?;
425                let value = seq
426                    .next_element()?
427                    .ok_or_else(|| SerdeError::invalid_length(1, &self))?;
428                Ok(KeyValue {
429                    key,
430                    value,
431                    _phantom: PhantomData,
432                })
433            }
434
435            fn visit_map<MapA: MapAccess<'de>>(
436                self,
437                mut map: MapA,
438            ) -> Result<KeyValue<K, V, N>, MapA::Error> {
439                let mut ret_key = None;
440                let mut ret_value = None;
441                while let Some(key) = map.next_key::<Field<N>>()? {
442                    match key {
443                        Field::Key => {
444                            if ret_key.is_some() {
445                                return Err(SerdeError::duplicate_field(N::KEY));
446                            }
447                            ret_key = Some(map.next_value()?);
448                        }
449                        Field::Value(_) => {
450                            if ret_value.is_some() {
451                                return Err(SerdeError::duplicate_field(N::VALUE));
452                            }
453                            ret_value = Some(map.next_value()?);
454                        }
455                    }
456                }
457                let key = ret_key.ok_or_else(|| SerdeError::missing_field(N::KEY))?;
458                let value = ret_value.ok_or_else(|| SerdeError::missing_field(N::VALUE))?;
459                Ok(KeyValue {
460                    key,
461                    value,
462                    _phantom: PhantomData,
463                })
464            }
465        }
466
467        deserializer.deserialize_struct("KeyValue", Self::FIELDS, KvVisitor::<_, _, N>(PhantomData))
468    }
469}
470
471#[cfg(feature = "json-schema")]
472impl<K, V, N> JsonSchema for KeyValue<K, V, N>
473where
474    K: JsonSchema,
475    V: JsonSchema,
476    N: KeyValueJsonSchema,
477{
478    fn schema_name() -> String {
479        N::JSON_SCHEMA_KV_NAME
480            .map(str::to_string)
481            .unwrap_or_else(|| {
482                format!("KeyValue_for_{}_and_{}", K::schema_name(), V::schema_name())
483            })
484    }
485
486    fn json_schema(gen: &mut SchemaGenerator) -> Schema {
487        let mut schema_object = SchemaObject {
488            instance_type: Some(InstanceType::Object.into()),
489            ..Default::default()
490        };
491        let object_validation = schema_object.object();
492
493        let mut key_schema = gen.subschema_for::<K>().into_object();
494        key_schema.metadata().description = N::JSON_SCHEMA_KEY_DESCRIPTION.map(str::to_string);
495        object_validation
496            .properties
497            .insert(N::KEY.to_string(), key_schema.into());
498        object_validation.required.insert(N::KEY.to_string());
499
500        let mut value_schema = gen.subschema_for::<V>().into_object();
501        value_schema.metadata().description = N::JSON_SCHEMA_VALUE_DESCRIPTION.map(str::to_string);
502        object_validation
503            .properties
504            .insert(N::VALUE.to_string(), value_schema.into());
505        object_validation.required.insert(N::VALUE.to_string());
506
507        schema_object.metadata().description = N::JSON_SCHEMA_KV_DESCRIPTION.map(str::to_string);
508        schema_object.into()
509    }
510}
511
512struct BTreeMapToArrayVisitor<K, V, N>(PhantomData<(K, V, N)>);
513
514impl<'de, K, V, N> Visitor<'de> for BTreeMapToArrayVisitor<K, V, N>
515where
516    K: Deserialize<'de> + Ord,
517    V: Deserialize<'de>,
518    N: KeyValueLabels,
519{
520    type Value = BTreeMap<K, V>;
521
522    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
523        formatter.write_str("a BTreeMapToArray")
524    }
525
526    fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
527        let mut map = BTreeMap::new();
528        while let Some(entry) = seq.next_element::<KeyValue<K, V, N>>()? {
529            map.insert(entry.key, entry.value);
530        }
531        Ok(map)
532    }
533}
534
535/// A converter between a `HashMap` or `HashMap`-like type and a sequence of named key-value pairs
536/// for human-readable encoding formats.
537///
538/// See [top-level docs](index.html) for example usage.
539#[cfg(feature = "std")]
540pub struct HashMapToArray<K, V, N = DefaultLabels, U = RandomState, T = HashMap<K, V, U>>(
541    PhantomData<(K, V, N, U, T)>,
542);
543
544#[cfg(feature = "std")]
545impl<K, V, N, U, T> HashMapToArray<K, V, N, U, T> {
546    /// Serializes the given `map` to an array of named key-values if the serializer is
547    /// human-readable.  Otherwise serializes the `map` as a map.
548    pub fn serialize<'a, S>(map: &'a T, serializer: S) -> Result<S::Ok, S::Error>
549    where
550        K: 'a + Serialize,
551        V: 'a + Serialize,
552        N: KeyValueLabels,
553        &'a T: IntoIterator<Item = (&'a K, &'a V)> + Serialize,
554        S: Serializer,
555    {
556        serialize::<K, V, N, T, S>(map, serializer)
557    }
558
559    /// Deserializes from a serialized array of named key-values into a map if the serializer is
560    /// human-readable.  Otherwise deserializes from a serialized map.
561    pub fn deserialize<'de, D>(deserializer: D) -> Result<T, D::Error>
562    where
563        K: Deserialize<'de> + Eq + Hash,
564        V: Deserialize<'de>,
565        N: KeyValueLabels,
566        U: BuildHasher + Default,
567        HashMap<K, V, U>: Into<T>,
568        T: Deserialize<'de>,
569        D: Deserializer<'de>,
570    {
571        let map = if deserializer.is_human_readable() {
572            deserializer.deserialize_seq(HashMapToArrayVisitor::<K, V, N, U>(PhantomData))
573        } else {
574            HashMap::<K, V, U>::deserialize(deserializer)
575        }?;
576        Ok(map.into())
577    }
578}
579
580#[cfg(feature = "json-schema")]
581impl<K, V, N, U, T> JsonSchema for HashMapToArray<K, V, N, U, T>
582where
583    K: JsonSchema,
584    V: JsonSchema,
585    N: KeyValueJsonSchema,
586{
587    fn schema_name() -> String {
588        Vec::<KeyValue<K, V, N>>::schema_name()
589    }
590
591    fn json_schema(gen: &mut SchemaGenerator) -> Schema {
592        Vec::<KeyValue<K, V, N>>::json_schema(gen)
593    }
594}
595
596#[cfg(feature = "std")]
597struct HashMapToArrayVisitor<K, V, N, U>(PhantomData<(K, V, N, U)>);
598
599#[cfg(feature = "std")]
600impl<'de, K, V, N, U> Visitor<'de> for HashMapToArrayVisitor<K, V, N, U>
601where
602    K: Deserialize<'de> + Eq + Hash,
603    V: Deserialize<'de>,
604    N: KeyValueLabels,
605    U: BuildHasher + Default,
606{
607    type Value = HashMap<K, V, U>;
608
609    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
610        formatter.write_str("a HashMapToArray")
611    }
612
613    fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
614        let mut map = HashMap::<K, V, U>::default();
615        while let Some(entry) = seq.next_element::<KeyValue<K, V, N>>()? {
616            map.insert(entry.key, entry.value);
617        }
618        Ok(map)
619    }
620}