Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support preserving order of parameters when serializing to a Map. #106

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ thiserror = "1.0"
tracing = { version = "0.1", optional = true }
warp-framework = { package = "warp", version = "0.3", default-features = false, optional = true }
axum-framework = { package = "axum", version = "0.7", default-features = false, optional = true }
indexmap = { version = "2.2", optional = true, features = ["serde"] }

[dev-dependencies]
chrono = { version = "0.4", features = ["serde"] }
Expand All @@ -40,10 +41,10 @@ actix2 = []
actix = []
warp = ["futures", "tracing", "warp-framework"]
axum = ["axum-framework", "futures"]
indexmap = ["dep:indexmap"]

[package.metadata.docs.rs]
features = ["actix4", "warp"]

[[example]]
name = "csv_vectors"
test = true
8 changes: 4 additions & 4 deletions src/de/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ use crate::error::*;
use serde::de;
use serde::de::IntoDeserializer;

use crate::map::{Entry, IntoIter, Map};
use std::borrow::Cow;
use std::collections::btree_map::{BTreeMap, Entry, IntoIter};

/// To override the default serialization parameters, first construct a new
/// Config.
Expand Down Expand Up @@ -202,16 +202,16 @@ pub struct QsDeserializer<'a> {

#[derive(Debug)]
enum Level<'a> {
Nested(BTreeMap<Cow<'a, str>, Level<'a>>),
OrderedSeq(BTreeMap<usize, Level<'a>>),
Nested(Map<Cow<'a, str>, Level<'a>>),
OrderedSeq(Map<usize, Level<'a>>),
Sequence(Vec<Level<'a>>),
Flat(Cow<'a, str>),
Invalid(String),
Uninitialised,
}

impl<'a> QsDeserializer<'a> {
fn with_map(map: BTreeMap<Cow<'a, str>, Level<'a>>) -> Self {
fn with_map(map: Map<Cow<'a, str>, Level<'a>>) -> Self {
QsDeserializer {
iter: map.into_iter(),
value: None,
Expand Down
12 changes: 6 additions & 6 deletions src/de/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ impl<'a> Level<'a> {
}
}
} else if let Level::Uninitialised = *self {
let mut map = BTreeMap::default();
let mut map = Map::default();
let _ = map.insert(key, Level::Flat(value));
*self = Level::Nested(map);
} else {
Expand All @@ -73,7 +73,7 @@ impl<'a> Level<'a> {
}
} else if let Level::Uninitialised = *self {
// To reach here, self is either an OrderedSeq or nothing.
let mut map = BTreeMap::default();
let mut map = Map::default();
let _ = map.insert(key, Level::Flat(value));
*self = Level::OrderedSeq(map);
} else {
Expand Down Expand Up @@ -274,14 +274,14 @@ impl<'a> Parser<'a> {
/// In some ways the main way to use a `Parser`, this runs the parsing step
/// and outputs a simple `Deserializer` over the parsed map.
pub(crate) fn as_deserializer(&mut self) -> Result<QsDeserializer<'a>> {
let map = BTreeMap::default();
let map = Map::new();
let mut root = Level::Nested(map);

// Parses all top level nodes into the `root` map.
while self.parse(&mut root)? {}
let iter = match root {
Level::Nested(map) => map.into_iter(),
_ => BTreeMap::default().into_iter(),
_ => Map::new().into_iter(),
};
Ok(QsDeserializer { iter, value: None })
}
Expand Down Expand Up @@ -450,7 +450,7 @@ impl<'a> Parser<'a> {
// The key continues to another level of nested.
// Add a new unitialised level for this node and continue.
if let Level::Uninitialised = *node {
*node = Level::Nested(BTreeMap::default());
*node = Level::Nested(Map::default());
}
if let Level::Nested(ref mut map) = *node {
// By parsing we drop down another level
Expand Down Expand Up @@ -527,7 +527,7 @@ impl<'a> Parser<'a> {
// The key continues to another level of nested.
// Add a new unitialised level for this node and continue.
if let Level::Uninitialised = *node {
*node = Level::OrderedSeq(BTreeMap::default());
*node = Level::OrderedSeq(Map::default());
}
if let Level::OrderedSeq(ref mut map) = *node {
// By parsing we drop down another level
Expand Down
18 changes: 18 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,3 +224,21 @@ pub mod axum;

#[cfg(feature = "warp")]
pub mod warp;

#[cfg(feature = "indexmap")]
mod indexmap {
pub use indexmap::map::{Entry, IntoIter};
pub use indexmap::IndexMap as Map;
}

#[cfg(feature = "indexmap")]
pub(crate) use indexmap as map;

#[cfg(not(feature = "indexmap"))]
mod btree_map {
pub use std::collections::btree_map::{Entry, IntoIter};
pub use std::collections::BTreeMap as Map;
}

#[cfg(not(feature = "indexmap"))]
pub(crate) use btree_map as map;
39 changes: 39 additions & 0 deletions tests/test_deserialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,45 @@ fn deserialize_map_with_unit_enum_keys() {
assert_eq!(test.point[&Operator::Lt], 321);
}

#[cfg(feature = "indexmap")]
#[test]
fn deserialize_map_with_unit_enum_keys_preserves_order() {
use indexmap::IndexMap;

#[derive(Deserialize, Eq, PartialEq, Hash, Debug)]
enum Key {
Name,
Age,
}

#[derive(Deserialize, Eq, PartialEq, Hash, Debug)]
enum Order {
Asc,
Desc,
}

#[derive(Deserialize)]
struct Sort {
sort: IndexMap<Key, Order>,
}

let test1: Sort = serde_qs::from_str("sort[Name]=Asc&sort[Age]=Desc").unwrap();
let values1 = test1.sort.into_iter().collect::<Vec<_>>();

assert_eq!(
values1,
vec![(Key::Name, Order::Asc), (Key::Age, Order::Desc)]
);

let test2: Sort = serde_qs::from_str("sort[Age]=Desc&sort[Name]=Asc").unwrap();
let values2 = test2.sort.into_iter().collect::<Vec<_>>();

assert_eq!(
values2,
vec![(Key::Age, Order::Desc), (Key::Name, Order::Asc)]
);
}

#[test]
fn deserialize_map_with_int_keys() {
#[derive(Debug, Deserialize)]
Expand Down