├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── rustfmt.toml ├── src └── lib.rs └── tests └── integration.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | target 4 | /build-out/ 5 | 6 | # Packages # 7 | ############ 8 | *.7z 9 | *.dmg 10 | *.gz 11 | *.iso 12 | *.jar 13 | *.rar 14 | *.tar 15 | *.zip 16 | 17 | # OS generated files # 18 | ###################### 19 | .DS_Store 20 | ehthumbs.db 21 | Icon? 22 | Thumbs.db 23 | 24 | # Project files # 25 | ################# 26 | .classpath 27 | .externalToolBuilders 28 | .idea 29 | .project 30 | .settings 31 | build 32 | dist 33 | nbproject 34 | atlassian-ide-plugin.xml 35 | build.xml 36 | nb-configuration.xml 37 | *.iml 38 | *.ipr 39 | *.iws 40 | *.sublime-project 41 | *.sublime-workspace 42 | Cargo.lock 43 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | sudo: false 3 | cache: cargo 4 | matrix: 5 | include: 6 | - rust: 1.13.0 7 | script: 8 | # packages used by our tests only support rust 1.32.0+ 9 | - cargo build --verbose 10 | - rust: 1.32.0 11 | script: 12 | - cargo build --verbose 13 | - cargo test --verbose 14 | # no-default-features requires rust 1.36.0+ 15 | - rust: 1.36.0 16 | script: 17 | - cargo build --verbose 18 | - cargo test --verbose 19 | - cargo build --verbose --no-default-features 20 | - cargo test --verbose --no-default-features 21 | - rust: stable 22 | script: 23 | - cargo build --verbose 24 | - cargo test --verbose 25 | - cargo build --verbose --no-default-features 26 | - cargo test --verbose --no-default-features 27 | - rust: beta 28 | script: 29 | - cargo build --verbose 30 | - cargo test --verbose 31 | - cargo build --verbose --no-default-features 32 | - cargo test --verbose --no-default-features 33 | - rust: nightly 34 | script: 35 | - cargo build --verbose 36 | - cargo test --verbose 37 | - cargo build --verbose --no-default-features 38 | - cargo test --verbose --no-default-features 39 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Unreleased 2 | ========== 3 | 4 | 5 | 1.0.1 (2022-04-04) 6 | ================== 7 | 8 | - Fix typo in vec initialization code which limited Vec pre-allocation 9 | size to 4069, rather than 4096 as was intended. 10 | 11 | 12 | 1.0.0 (2019-02-08) 13 | ================== 14 | 15 | - Add support for serializing from (but not deserializing to) `&[(K, V)]` 16 | - Specify more exact behavior in documentation 17 | - Migrate from git tag messages to an explicit CHANGELOG.md file 18 | 19 | 20 | 0.2.2 (2019-02-08) 21 | ================== 22 | 23 | - Remove reliance on nightly rust, following the stabilization of the 24 | 'alloc' library. 25 | 26 | When using default-features = false, this crate now no longer depends 27 | on nightly, but instead on Rust 1.36.0+. 28 | 29 | As the prior dependency was on an unstable nightly feature, I don't 30 | consider this a breaking change. 31 | - Update CI configurations to specify and test the minimum suppored rust 32 | version of 1.13.0 when default-features is enabled. 33 | 34 | 35 | 0.2.1 (2018-01-04) 36 | ================== 37 | 38 | - Update documentation links to point to docs.rs 39 | - Clean up documentation, make it more informative and less like a 40 | sales pitch. 41 | 42 | 43 | 0.2.0 (2017-04-26) 44 | ================== 45 | 46 | - Update from serde 0.9 to serde 1.0 stable 47 | 48 | 49 | 0.1.0 (2017-03-28) 50 | ================== 51 | 52 | - Initial release! Fully working. 53 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "serde-tuple-vec-map" 3 | # Remember to update html_root_url in src/lib.rs with each version. 4 | version = "1.0.1" 5 | authors = ["David Ross "] 6 | description = "Serialize and deserialize Vec<(K, V)> as if it were a map in serde" 7 | 8 | documentation = "https://docs.rs/tuple-vec-map/" 9 | repository = "https://github.com/daboross/serde-tuple-vec-map/" 10 | readme = "README.md" 11 | 12 | license = "MIT" 13 | keywords = ["serde", "vec", "hashmap"] 14 | categories = ["encoding"] 15 | 16 | include = ["Cargo.toml", "src/**/*", "tests/**/*", "examples/**/*", "LICENSE", "README.md", "CHANGELOG.md"] 17 | 18 | [lib] 19 | name = "tuple_vec_map" 20 | path = "src/lib.rs" 21 | 22 | [features] 23 | std = [] 24 | default = ["std"] 25 | 26 | [dependencies] 27 | serde = { version = "1.0", default-features = false } 28 | 29 | [dev-dependencies] 30 | serde_derive = "1.0" 31 | serde_json = "1.0" 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 David Ross 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | serde-tuple-vec-map 2 | ================ 3 | [![Build Status][travis-image]][travis-builds] 4 | [![crates.io version badge][cratesio-badge]][cratesio-page] 5 | 6 | Deserialize maps or JSON objects in [serde] to a vec of tuples rather than a 7 | HashMap for when you're only ever going to iterate over the result. 8 | 9 | Usage: 10 | 11 | ```rust 12 | // replace this: 13 | #[derive(Serialize, Deserialize)] 14 | struct MyStuff { 15 | data: HashMap, 16 | } 17 | 18 | // with this: 19 | #[derive(Serialize, Deserialize)] 20 | struct MyStuff { 21 | #[serde(with = "tuple_vec_map")] 22 | data: Vec<(KeyType, ValueType)>, 23 | } 24 | ``` 25 | 26 | The serialized format remains exactly the same, the only difference is in how 27 | the data is decoded in Rust. 28 | 29 | serde-tuple-vec-map supports no_std builds by using `Vec` defined in the `alloc` 30 | crate. If you're on rust 1.36.0 or newer, you can enable this with 31 | `default-features=false`: 32 | 33 | ```toml 34 | [dependencies.serde-tuple-vec-map] 35 | version = "1" 36 | default-features = false 37 | ``` 38 | 39 | Note: This crate is complete, and passively maintained. It depends solely on 40 | `serde` and features present in stable rust. The minimum supported rust 41 | version 1.13.0 when using default features, and 1.36.0 for 42 | `default-features = false`. 43 | 44 | Full usage example in [`tests/integration.rs`][example], documentation at 45 | https://docs.rs/serde-tuple-vec-map. 46 | 47 | [travis-image]: https://travis-ci.org/daboross/serde-tuple-vec-map.svg?branch=master 48 | [travis-builds]: https://travis-ci.org/daboross/serde-tuple-vec-map 49 | [serde]: https://github.com/serde-rs/serde/ 50 | [cratesio-badge]: http://meritbadge.herokuapp.com/serde-tuple-vec-map 51 | [cratesio-page]: https://crates.io/crates/serde-tuple-vec-map 52 | [example]: https://github.com/daboross/serde-tuple-vec-map/blob/1.0.1/tests/integration.rs 53 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | use_try_shorthand = true 2 | condense_wildcard_suffixes = true 3 | format_strings = false 4 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Deserializing maps to tuple-vecs. 2 | //! 3 | //! To use, just include a [`Vec<(String, ...)>`][Vec] in your struct instead of a 4 | //! [`HashMap`][std::collections::HashMap] and tag it with `#[serde(with = "tuple_vec_map")`: 5 | //! 6 | //! ``` 7 | //! # extern crate serde; 8 | //! # #[macro_use] extern crate serde_derive; 9 | //! extern crate tuple_vec_map; 10 | //! 11 | //! #[derive(Serialize, Deserialize)] 12 | //! struct SomeData { 13 | //! other_stuff: u32, 14 | //! #[serde(with = "tuple_vec_map")] 15 | //! inner_data: Vec<(String, String)>, 16 | //! } 17 | //! # fn main() {} 18 | //! ``` 19 | //! 20 | //! That's it! Now your structure accepts an inner_data Map or JSON Object, and instead of making 21 | //! a [`HashMap`][std::collections::HashMap] for the data, the key/value pairs are simply collected into a [`vec`]. 22 | //! 23 | //! ## Features 24 | //! 25 | //! To use without `std`, depend on `serde-tuple-vec-map` with `default-features = false`. This will still 26 | //! depend on the `alloc` crate, and requires Rust 1.36.0 or newer. 27 | #![cfg_attr(not(feature = "std"), no_std)] 28 | #![deny(missing_docs)] 29 | #![doc(html_root_url = "https://docs.rs/serde-tuple-vec-map/1.0.1")] 30 | 31 | extern crate serde; 32 | 33 | #[cfg(not(feature = "std"))] 34 | extern crate alloc; 35 | 36 | #[cfg(feature = "std")] 37 | mod core { 38 | // this mirrors serde's setup for std/non-std. 39 | pub use std::cmp; 40 | pub use std::fmt; 41 | pub use std::marker; 42 | pub use std::ops; 43 | } 44 | 45 | use core::fmt::Debug; 46 | use core::marker::PhantomData; 47 | use core::ops::Deref; 48 | use core::{cmp, fmt}; 49 | 50 | use serde::de::{MapAccess, Visitor}; 51 | use serde::{Deserialize, Deserializer, Serialize, Serializer}; 52 | 53 | #[cfg(not(feature = "std"))] 54 | use alloc::vec::Vec; 55 | 56 | struct TupleVecMapVisitor { 57 | marker: PhantomData>, 58 | } 59 | 60 | impl TupleVecMapVisitor { 61 | fn new() -> Self { 62 | TupleVecMapVisitor { 63 | marker: PhantomData, 64 | } 65 | } 66 | } 67 | 68 | impl<'de, K, V> Visitor<'de> for TupleVecMapVisitor 69 | where 70 | K: Deserialize<'de>, 71 | V: Deserialize<'de>, 72 | { 73 | type Value = Vec<(K, V)>; 74 | 75 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 76 | formatter.write_str("a map") 77 | } 78 | 79 | #[inline] 80 | fn visit_unit(self) -> Result, E> { 81 | Ok(Vec::new()) 82 | } 83 | 84 | #[inline] 85 | fn visit_map(self, mut access: T) -> Result, T::Error> 86 | where 87 | T: MapAccess<'de>, 88 | { 89 | let mut values = Vec::with_capacity(cmp::min(access.size_hint().unwrap_or(0), 4096)); 90 | 91 | while let Some((key, value)) = access.next_entry()? { 92 | values.push((key, value)); 93 | } 94 | 95 | Ok(values) 96 | } 97 | } 98 | 99 | /// Serialize a [`slice`] of `(K, V)` pairs as if it were a [`HashMap`][std::collections::HashMap]. 100 | /// 101 | /// In formats where dictionaries are ordered, this maintains the input data's order. Each pair is treated as a single 102 | /// entry into the dictionary. 103 | /// 104 | /// Behavior when duplicate keys are present in the data is unspecified and serializer-dependent. This function does 105 | /// not check for duplicate keys and will not warn the serializer. 106 | /// 107 | /// # Errors 108 | /// 109 | /// Errors if and only if the given serializer emits an error. 110 | pub fn serialize(data: &[(K, V)], serializer: S) -> Result 111 | where 112 | S: Serializer, 113 | K: Serialize, 114 | V: Serialize, 115 | { 116 | serializer.collect_map(data.iter().map(|x| (&x.0, &x.1))) 117 | } 118 | 119 | /// Deserialize to a [`Vec<(K, V)>`] as if it were a [`HashMap`][std::collections::HashMap]. 120 | /// 121 | /// This directly deserializes into the returned vec with no intermediate allocation. 122 | /// 123 | /// In formats where dictionaries are ordered, this maintains the input data's order. 124 | /// 125 | /// # Errors 126 | /// 127 | /// Errors if and only if the given deserializer emits an error. Note, this may occur if using this function to 128 | /// deserialize data that the serializer can't treat as a map. 129 | pub fn deserialize<'de, K, V, D>(deserializer: D) -> Result, D::Error> 130 | where 131 | D: Deserializer<'de>, 132 | K: Deserialize<'de>, 133 | V: Deserialize<'de>, 134 | { 135 | deserializer.deserialize_map(TupleVecMapVisitor::new()) 136 | } 137 | 138 | /// Vec-as-map serialization wrapper. 139 | /// 140 | /// This is intended when `tuple_vec_map` behavior is required for the outermost serialized or deserialized object. 141 | /// 142 | /// While [`Wrapper`] can be constructed with any inner value, it only implements useful traits when the inner value is 143 | /// either `Vec<(K, V)>` or `&[(K, V)]` for some `K` and `V`. Thus, utility methods [`Wrapper::from_slice`] and 144 | /// [`Wrapper::from_vec`] have been created for convenient type-error-free construction. 145 | /// 146 | /// # Serialization 147 | /// 148 | /// When `T` can be treated as `&[(K, V)]`, `Wrapper` implements [`Serialize`], serializing the inner value as if it 149 | /// were a [`HashMap`][std::collections::HashMap]. 150 | /// 151 | /// As an example, this works for `Wrapper<&[(K, V)]>`, `Wrapper>`, `Wrapper<[(K, V); N]>`, as well as 152 | /// other Vec-like types, such as `Wrapper>` using 153 | /// [`SmallVec`](https://docs.rs/smallvec/latest/smallvec/struct.SmallVec.html) from the 154 | /// [`smallvec`](https://docs.rs/smallvec/) crate. 155 | /// 156 | /// In formats where dictionaries are ordered, this maintains the input data's order. Each pair is treated as a single 157 | /// entry into the dictionary. 158 | /// 159 | /// Behavior when duplicate keys are present in the data is unspecified and serializer-dependent. This function does 160 | /// not check for duplicate keys and will not warn the serializer. 161 | /// 162 | /// ## Example 163 | /// 164 | /// ``` 165 | /// # fn main() -> Result<(), Box> { 166 | /// let data = [("hello", "world"), ("answer", "42")]; 167 | /// let out = serde_json::to_string(&tuple_vec_map::Wrapper::from_slice(&data))?; 168 | /// assert_eq!(out, r#"{"hello":"world","answer":"42"}"#); 169 | /// # Ok(()) } 170 | /// ``` 171 | /// 172 | /// # Deserialization 173 | /// 174 | /// `Wrapper>` implements [`Deserialize`], deserializing from a map as if it were a 175 | /// [`HashMap`][std::collections::HashMap]. 176 | /// 177 | /// This directly deserializes into the wrapped vec with no intermediate allocation. 178 | /// 179 | /// In formats where dictionaries are ordered, this maintains the input data's order. 180 | /// 181 | /// ## Example 182 | /// 183 | /// ``` 184 | /// # fn main() -> Result<(), Box> { 185 | /// let data = r#"{"hello": "world", "answer": "42"}"#; 186 | /// let out: tuple_vec_map::Wrapper> = serde_json::from_str(data)?; 187 | /// assert_eq!( 188 | /// out.into_inner(), 189 | /// vec![ 190 | /// ("hello".to_owned(), "world".to_owned()), 191 | /// ("answer".to_owned(), "42".to_owned()), 192 | /// ], 193 | /// ); 194 | /// # Ok(()) } 195 | /// ``` 196 | #[derive(Clone, Copy)] 197 | pub struct Wrapper( 198 | /// The inner value, either to be serialized or freshly deserialized. 199 | pub T, 200 | ); 201 | 202 | impl<'a, K, V> Wrapper<&'a [(K, V)]> { 203 | /// Creates a wrapper from the given slice. 204 | pub fn from_slice(slice: &'a [(K, V)]) -> Self { 205 | Wrapper(slice) 206 | } 207 | } 208 | 209 | impl Wrapper> { 210 | /// Creates a wrapper from the given [`Vec`]. 211 | #[must_use] 212 | pub fn from_vec(vec: Vec<(K, V)>) -> Self { 213 | Wrapper(vec) 214 | } 215 | } 216 | 217 | impl Wrapper { 218 | /// Takes the inner value out of this [`Wrapper`]. 219 | pub fn into_inner(self) -> T { 220 | self.0 221 | } 222 | } 223 | 224 | impl Debug for Wrapper { 225 | fn fmt<'a>(&self, f: &mut fmt::Formatter<'a>) -> fmt::Result { 226 | self.0.fmt(f) 227 | } 228 | } 229 | 230 | impl<'a, T, K, V> Serialize for Wrapper 231 | where 232 | T: Deref, 233 | K: 'a + Serialize, 234 | V: 'a + Serialize, 235 | { 236 | fn serialize(&self, serializer: S) -> Result 237 | where 238 | S: Serializer, 239 | { 240 | serializer.collect_map(self.0.iter().map(|x| (&x.0, &x.1))) 241 | } 242 | } 243 | 244 | impl<'de, K, V> Deserialize<'de> for Wrapper> 245 | where 246 | K: Deserialize<'de>, 247 | V: Deserialize<'de>, 248 | { 249 | fn deserialize(deserializer: D) -> Result 250 | where 251 | D: Deserializer<'de>, 252 | { 253 | deserializer 254 | .deserialize_map(TupleVecMapVisitor::new()) 255 | .map(Wrapper) 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /tests/integration.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "std"), no_std)] 2 | 3 | #[macro_use] 4 | extern crate serde_derive; 5 | #[macro_use] 6 | extern crate serde_json; 7 | 8 | #[cfg(not(feature = "std"))] 9 | extern crate alloc; 10 | 11 | extern crate serde; 12 | extern crate tuple_vec_map; 13 | 14 | use serde::Deserialize; 15 | 16 | #[cfg(not(feature = "std"))] 17 | use alloc::borrow::ToOwned; 18 | #[cfg(not(feature = "std"))] 19 | use alloc::string::String; 20 | #[cfg(not(feature = "std"))] 21 | use alloc::vec; 22 | #[cfg(not(feature = "std"))] 23 | use alloc::vec::Vec; 24 | 25 | #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] 26 | struct TestData { 27 | #[serde(with = "tuple_vec_map")] 28 | score: Vec<(String, u32)>, 29 | } 30 | 31 | #[test] 32 | fn test_deserialization() { 33 | let data = json!({ 34 | "score": { 35 | "user1": 50, 36 | "user2": 30, 37 | "user3": 0, 38 | "user4": 100, 39 | } 40 | }); 41 | 42 | let deserialized = TestData::deserialize(data).expect("expected successful deserialization"); 43 | 44 | // Note: This also tests that order is maintained. 45 | assert_eq!( 46 | deserialized, 47 | TestData { 48 | score: vec![ 49 | ("user1".to_owned(), 50), 50 | ("user2".to_owned(), 30), 51 | ("user3".to_owned(), 0), 52 | ("user4".to_owned(), 100), 53 | ], 54 | } 55 | ) 56 | } 57 | 58 | #[test] 59 | fn test_serialization() { 60 | let data = TestData { 61 | score: vec![ 62 | ("a_guy".to_owned(), 200), 63 | ("b_guy".to_owned(), 300), 64 | ("c_guy".to_owned(), 400), 65 | ], 66 | }; 67 | 68 | let serialized = serde_json::to_value(data).expect("expected data to serialize successfully"); 69 | 70 | assert_eq!( 71 | serialized, 72 | json!({ 73 | "score": { 74 | "a_guy": 200, 75 | "b_guy": 300, 76 | "c_guy": 400, 77 | } 78 | }) 79 | ); 80 | } 81 | 82 | #[test] 83 | fn serializing_from_slice_works() { 84 | #[derive(Serialize)] 85 | struct Data<'a> { 86 | #[serde(with = "tuple_vec_map")] 87 | inner: &'a [(&'a str, &'a str)], 88 | } 89 | 90 | let data = Data { 91 | inner: &[("answer", "fourty-two")], 92 | }; 93 | 94 | let ser = serde_json::to_value(data).unwrap(); 95 | assert_eq!( 96 | ser, 97 | json!({ 98 | "inner": { 99 | "answer": "fourty-two", 100 | } 101 | }) 102 | ); 103 | } 104 | 105 | #[test] 106 | fn serializing_raw() { 107 | let data: &[(&str, &str)] = &[("answer", "fourty-two")]; 108 | 109 | let ser = serde_json::to_value(tuple_vec_map::Wrapper(data)).unwrap(); 110 | assert_eq!( 111 | ser, 112 | json!({ 113 | "answer": "fourty-two", 114 | }) 115 | ); 116 | } 117 | 118 | #[test] 119 | fn wrapper_round_trip() { 120 | // this works, and should be equivalent to `#[serde(with = "tuple_vec_map")]`. With that said, this should be 121 | // considered an anti-pattern in real code, with `#[serde(with = "tuple_vec_map")]` preferred when possible. 122 | 123 | #[derive(Serialize, Deserialize, Debug)] 124 | struct TestDataUsingWrapper { 125 | score: tuple_vec_map::Wrapper>, 126 | } 127 | 128 | let data = json!({ 129 | "score": { 130 | "user1": 50, 131 | "user2": 30, 132 | "user3": 0, 133 | "user4": 100, 134 | } 135 | }); 136 | 137 | let deserialized = TestDataUsingWrapper::deserialize(data.clone()) 138 | .expect("expected successful deserialization"); 139 | 140 | // Note: This also tests that order is maintained. 141 | assert_eq!( 142 | &deserialized.score.0, 143 | &[ 144 | ("user1".to_owned(), 50), 145 | ("user2".to_owned(), 30), 146 | ("user3".to_owned(), 0), 147 | ("user4".to_owned(), 100), 148 | ], 149 | ); 150 | 151 | let reserialized = serde_json::to_value(deserialized).unwrap(); 152 | assert_eq!(reserialized, data); 153 | } 154 | --------------------------------------------------------------------------------