diff --git a/Cargo.toml b/Cargo.toml index dfbea0a..cd89514 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,11 @@ include = [ [badges] maintenance = { status = "passively-maintained" } +[features] +ordered-map = ["dep:indexmap"] + [dependencies] +indexmap = { version = "2.4", optional = true } [dev-dependencies] walkdir = "2" diff --git a/bench/benches/generate.rs b/bench/benches/generate.rs index 64e485f..307f608 100644 --- a/bench/benches/generate.rs +++ b/bench/benches/generate.rs @@ -1,6 +1,5 @@ use criterion::{criterion_group, criterion_main, Criterion}; -use std::collections::HashMap; -use tinyjson::JsonValue; +use tinyjson::{JsonMap, JsonValue}; fn generate(c: &mut Criterion) { c.bench_function("generate::string", |b| { @@ -36,7 +35,7 @@ fn generate(c: &mut Criterion) { }); }); c.bench_function("generate::object", |b| { - let mut kv = HashMap::new(); + let mut kv = JsonMap::new(); kv.insert("num".into(), 123.45.into()); kv.insert("bool".into(), true.into()); kv.insert("str".into(), "this is test".to_string().into()); diff --git a/src/generator.rs b/src/generator.rs index e01c50d..60779e1 100644 --- a/src/generator.rs +++ b/src/generator.rs @@ -1,5 +1,4 @@ -use crate::JsonValue; -use std::collections::HashMap; +use crate::{JsonMap, JsonValue}; use std::fmt; use std::io::{self, Write}; @@ -162,7 +161,7 @@ impl<'indent, W: Write> JsonGenerator<'indent, W> { self.out.write_all(b"]") } - fn encode_object(&mut self, m: &HashMap) -> io::Result<()> { + fn encode_object(&mut self, m: &JsonMap) -> io::Result<()> { self.out.write_all(b"{")?; let mut first = true; for (k, v) in m { @@ -219,7 +218,7 @@ impl<'indent, W: Write> JsonGenerator<'indent, W> { fn format_object( &mut self, - m: &HashMap, + m: &JsonMap, indent: &str, level: usize, ) -> io::Result<()> { diff --git a/src/json_value.rs b/src/json_value.rs index 4cec88f..b571254 100644 --- a/src/json_value.rs +++ b/src/json_value.rs @@ -1,13 +1,23 @@ use crate::generator::{format, stringify, JsonGenerateResult, JsonGenerator}; use crate::query::{JsonQuery, JsonQueryMut}; -use std::collections::HashMap; use std::convert::TryFrom; use std::fmt; use std::io; use std::ops::{Index, IndexMut}; +#[cfg(feature = "ordered-map")] +use indexmap::IndexMap; +#[cfg(not(feature = "ordered-map"))] +use std::collections::HashMap; + const NULL: () = (); +#[cfg(feature = "ordered-map")] +pub type JsonMap = IndexMap; +#[cfg(not(feature = "ordered-map"))] +pub type JsonMap = HashMap; + + /// Enum to represent one JSON value. Each variant represents corresponding JSON types. /// ``` /// use tinyjson::JsonValue; @@ -42,7 +52,7 @@ pub enum JsonValue { /// Array type value. Array(Vec), /// Object type value. - Object(HashMap), + Object(JsonMap), } /// Trait to access to inner value of `JsonValue` as reference. @@ -71,7 +81,7 @@ impl_inner_ref!(bool, Boolean(b) => b); impl_inner_ref!(String, String(s) => s); impl_inner_ref!((), Null => &NULL); impl_inner_ref!(Vec, Array(a) => a); -impl_inner_ref!(HashMap, Object(h) => h); +impl_inner_ref!(JsonMap, Object(h) => h); /// Trait to access to inner value of `JsonValue` as mutable reference. /// @@ -98,7 +108,7 @@ impl_inner_ref_mut!(f64, Number(n) => n); impl_inner_ref_mut!(bool, Boolean(b) => b); impl_inner_ref_mut!(String, String(s) => s); impl_inner_ref_mut!(Vec, Array(a) => a); -impl_inner_ref_mut!(HashMap, Object(h) => h); +impl_inner_ref_mut!(JsonMap, Object(h) => h); // Note: matches! is available from Rust 1.42 macro_rules! is_xxx { @@ -241,16 +251,15 @@ impl JsonValue { /// allows to write `if` guard if you use Rust 1.42.0 or later. /// /// ``` - /// use tinyjson::JsonValue; - /// use std::collections::HashMap; + /// use tinyjson::{JsonMap, JsonValue}; /// - /// let v = JsonValue::from(HashMap::new()); + /// let v = JsonValue::from(JsonMap::new()); /// assert!(v.is_object()); /// let v = JsonValue::from(vec![]); /// assert!(!v.is_object()); /// /// // matches! macro may be better choice - /// let mut m = HashMap::new(); + /// let mut m = JsonMap::new(); /// m.insert("hello".to_string(), "world".to_string().into()); /// let v = JsonValue::from(m); /// assert!(matches!(&v, JsonValue::Object(o) if o.contains_key("hello"))); @@ -388,10 +397,9 @@ impl JsonValue { /// Access the element value of the key of object. /// /// ``` -/// use tinyjson::JsonValue; -/// use std::collections::HashMap; +/// use tinyjson::{JsonMap, JsonValue}; /// -/// let mut m = HashMap::new(); +/// let mut m = JsonMap::new(); /// m.insert("foo".to_string(), 1.0.into()); /// let v = JsonValue::from(m); /// let i = &v["foo"]; @@ -409,9 +417,8 @@ impl JsonValue { /// or when the key does not exist in the object. /// /// ```should_panic -/// # use tinyjson::JsonValue; -/// # use std::collections::HashMap; -/// let v = JsonValue::from(HashMap::new()); +/// # use tinyjson::{JsonMap, JsonValue}; +/// let v = JsonValue::from(JsonMap::new()); /// let _ = &v["foo"]; // Panic /// ``` /// @@ -465,9 +472,8 @@ impl<'a> Index<&'a str> for JsonValue { /// Like standard containers such as `Vec` or `HashMap`, it will panic when the given `JsonValue` value is not an array /// /// ```should_panic -/// # use tinyjson::JsonValue; -/// use std::collections::HashMap; -/// let v = JsonValue::from(HashMap::new()); +/// # use tinyjson::{JsonMap, JsonValue}; +/// let v = JsonValue::from(JsonMap::new()); /// let _ = &v[0]; // Panic /// ``` /// @@ -496,10 +502,9 @@ impl Index for JsonValue { /// Access the element value of the key of mutable object. /// /// ``` -/// use tinyjson::JsonValue; -/// use std::collections::HashMap; +/// use tinyjson::{JsonMap, JsonValue}; /// -/// let mut m = HashMap::new(); +/// let mut m = JsonMap::new(); /// m.insert("foo".to_string(), 1.0.into()); /// let mut v = JsonValue::from(m); /// v["foo"] = JsonValue::Number(3.14); @@ -517,9 +522,8 @@ impl Index for JsonValue { /// or when the key does not exist in the object. /// /// ```should_panic -/// # use tinyjson::JsonValue; -/// # use std::collections::HashMap; -/// let mut v = JsonValue::from(HashMap::new()); +/// # use tinyjson::{JsonMap, JsonValue}; +/// let mut v = JsonValue::from(JsonMap::new()); /// let _ = &mut v["foo"]; // Panic /// ``` /// @@ -572,9 +576,8 @@ impl<'a> IndexMut<&'a str> for JsonValue { /// Like standard containers such as `Vec` or `HashMap`, it will panic when the given `JsonValue` value is not an array /// /// ```should_panic -/// # use tinyjson::JsonValue; -/// use std::collections::HashMap; -/// let mut v = JsonValue::from(HashMap::new()); +/// # use tinyjson::{JsonMap, JsonValue}; +/// let mut v = JsonValue::from(JsonMap::new()); /// let _ = &mut v[0]; // Panic /// ``` /// @@ -666,17 +669,16 @@ impl_from!( a: Vec => Array(a) ); impl_from!( - /// Convert `HashMap` value into `JsonValue`. + /// Convert `JsonMap` value into `JsonValue`. /// /// ``` - /// use tinyjson::JsonValue; - /// use std::collections::HashMap; - /// let mut m = HashMap::new(); + /// use tinyjson::{JsonMap, JsonValue}; + /// let mut m = JsonMap::new(); /// m.insert("foo".to_string(), 1.0.into()); /// let v = JsonValue::from(m); /// assert!(v.is_object()); /// ``` - o: HashMap => Object(o) + o: JsonMap => Object(o) ); /// Error caused when trying to convert `JsonValue` into some wrong type value. @@ -855,24 +857,23 @@ impl_try_from!( Vec, ); impl_try_from!( - /// Try to convert the `JsonValue` value into `HashMap`. `UnexpectedValue` error happens when + /// Try to convert the `JsonValue` value into `JsonMap`. `UnexpectedValue` error happens when /// trying to convert an incorrect type value. /// /// ``` - /// use tinyjson::JsonValue; + /// use tinyjson::{JsonMap, JsonValue}; /// use std::convert::TryFrom; - /// use std::collections::HashMap; /// - /// let mut m = HashMap::new(); + /// let mut m = JsonMap::new(); /// m.insert("foo".to_string(), 42.0.into()); /// let v = JsonValue::from(m); - /// let r = >::try_from(v); + /// let r = ::try_from(v); /// assert!(r.is_ok()); /// /// let v = JsonValue::from(1.0); - /// let r = >::try_from(v); + /// let r = ::try_from(v); /// assert!(r.is_err()); /// ``` JsonValue::Object(o) => o, - HashMap, + JsonMap, ); diff --git a/src/lib.rs b/src/lib.rs index 3174e78..927c92a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,8 +19,7 @@ //! Example: //! //! ``` -//! use tinyjson::JsonValue; -//! use std::collections::HashMap; +//! use tinyjson::{JsonMap, JsonValue}; //! use std::convert::TryInto; //! //! let s = r#" @@ -39,8 +38,8 @@ //! let parsed: JsonValue = s.parse().unwrap(); //! //! // Access to inner value represented with standard containers -//! let object: &HashMap<_, _> = parsed.get().unwrap(); -//! println!("Parsed HashMap: {:?}", object); +//! let object: &JsonMap = parsed.get().unwrap(); +//! println!("Parsed map: {:?}", object); //! //! // Generate JSON string //! println!("{}", parsed.stringify().unwrap()); @@ -52,11 +51,11 @@ //! println!("Second element of \"arr\": {:?}", elem); //! //! // Convert to inner value represented with standard containers -//! let object: HashMap<_, _> = parsed.try_into().unwrap(); -//! println!("Converted into HashMap: {:?}", object); +//! let object: JsonMap = parsed.try_into().unwrap(); +//! println!("Converted into map: {:?}", object); //! //! // Create JSON values from standard containers -//! let mut m = HashMap::new(); +//! let mut m = JsonMap::new(); //! m.insert("foo".to_string(), true.into()); //! let mut v = JsonValue::from(m); //! @@ -68,14 +67,14 @@ //! //! Any JSON value is represented with [`JsonValue`] enum. Each JSON types are mapped to Rust types as follows: //! -//! | JSON | Rust | -//! |---------|------------------------------| -//! | Number | `f64` | -//! | Boolean | `bool` | -//! | String | `String` | -//! | Null | `()` | -//! | Array | `Vec` | -//! | Object | `HashMap` | +//! | JSON | Rust | +//! |---------|---------------------------------------------------------------| +//! | Number | `f64` | +//! | Boolean | `bool` | +//! | String | `String` | +//! | Null | `()` | +//! | Array | `Vec` | +//! | Object | `HashMap` or `IndexMap` | //! //! Flexible query APIs are available to access nested elements easily without panic. See [`JsonQuery`] and //! [`JsonQueryMut`] for more details. @@ -92,6 +91,6 @@ mod parser; mod query; pub use generator::*; -pub use json_value::{InnerAsRef, InnerAsRefMut, JsonValue, UnexpectedValue}; +pub use json_value::{InnerAsRef, InnerAsRefMut, JsonMap, JsonValue, UnexpectedValue}; pub use parser::*; pub use query::{ChildIndex, JsonQuery, JsonQueryMut}; diff --git a/src/parser.rs b/src/parser.rs index 262d8a6..ee6bd5f 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,10 +1,9 @@ use std::char; -use std::collections::HashMap; use std::fmt; use std::iter::Peekable; use std::str::FromStr; -use crate::JsonValue; +use crate::{JsonValue, JsonMap}; /// Parse error. /// @@ -172,10 +171,10 @@ impl> JsonParser { if self.peek()? == '}' { self.consume().unwrap(); - return Ok(JsonValue::Object(HashMap::new())); + return Ok(JsonValue::Object(JsonMap::new())); } - let mut m = HashMap::new(); + let mut m = JsonMap::new(); loop { let key = match self.parse_any()? { JsonValue::String(s) => s, diff --git a/tests/generator.rs b/tests/generator.rs index c58e50a..be902b9 100644 --- a/tests/generator.rs +++ b/tests/generator.rs @@ -1,6 +1,5 @@ -use std::collections::HashMap; use std::f64; -use tinyjson::JsonValue; +use tinyjson::{JsonMap, JsonValue}; #[test] fn test_number() { @@ -61,7 +60,7 @@ fn test_array() { #[test] fn test_object() { - let mut m = HashMap::new(); + let mut m = JsonMap::new(); m.insert("foo".to_string(), JsonValue::Number(1.0)); m.insert("bar".to_string(), JsonValue::Boolean(false)); m.insert("piyo".to_string(), JsonValue::Null); @@ -72,7 +71,7 @@ fn test_object() { assert!(s.contains(r#""bar":false"#)); assert!(s.contains(r#""piyo":null"#)); assert!(s.ends_with('}')); - let v = JsonValue::Object(HashMap::new()); + let v = JsonValue::Object(JsonMap::new()); let s = v.stringify().unwrap(); assert_eq!(&s, "{}"); } @@ -109,7 +108,7 @@ fn test_format_array() { JsonValue::Array(vec![ JsonValue::Array(vec![ { - let mut m = HashMap::new(); + let mut m = JsonMap::new(); m.insert("foo".to_string(), JsonValue::String("bar".to_string())); JsonValue::Object(m) }, @@ -146,7 +145,7 @@ fn test_format_array() { #[test] fn test_format_object() { - let mut m = HashMap::new(); + let mut m = JsonMap::new(); m.insert("foo".to_string(), JsonValue::Number(1.0)); m.insert("bar".to_string(), JsonValue::Boolean(false)); m.insert("piyo".to_string(), JsonValue::Null); @@ -157,7 +156,7 @@ fn test_format_object() { assert!(s.contains(r#" "bar": false"#)); assert!(s.contains(r#" "piyo": null"#)); assert!(s.ends_with('}')); - let v = JsonValue::Object(HashMap::new()); + let v = JsonValue::Object(JsonMap::new()); let s = v.format().unwrap(); assert_eq!(&s, "{}"); } diff --git a/tests/query.rs b/tests/query.rs index afbd8b6..c94ffb7 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -1,4 +1,3 @@ -use std::collections::HashMap; use tinyjson::*; #[test] @@ -111,11 +110,11 @@ fn test_query_object_key() { ); assert_eq!( v.query().child("a").child("d").find().unwrap(), - &JsonValue::Object(HashMap::new()), + &JsonValue::Object(JsonMap::new()), ); assert_eq!( v.query().child("e").find().unwrap(), - &JsonValue::Object(HashMap::new()), + &JsonValue::Object(JsonMap::new()), ); assert_eq!( @@ -155,11 +154,11 @@ fn test_query_mut_object_key() { ); assert_eq!( v.query_mut().child("a").child("d").find().unwrap(), - &mut JsonValue::Object(HashMap::new()), + &mut JsonValue::Object(JsonMap::new()), ); assert_eq!( v.query_mut().child("e").find().unwrap(), - &mut JsonValue::Object(HashMap::new()), + &mut JsonValue::Object(JsonMap::new()), ); assert_eq!( @@ -197,7 +196,7 @@ fn test_query_mut_object_key() { fn test_query_value_predicate() { let v: JsonValue = r#"[{"a": 0, "b": 1}, 0, 1, 2]"#.parse().unwrap(); let a: &Vec<_> = v.get().unwrap(); - let m: &HashMap<_, _> = a[0].get().unwrap(); + let m: &JsonMap = a[0].get().unwrap(); assert_eq!(v.query().child_by(|v| v.is_object()).get(), Some(m)); assert_eq!(v.query().child_by(|v| v.is_number()).find(), Some(&a[1])); diff --git a/tests/value.rs b/tests/value.rs index 1af8b7f..b030cdf 100644 --- a/tests/value.rs +++ b/tests/value.rs @@ -1,4 +1,3 @@ -use std::collections::HashMap; use tinyjson::*; const STR_OK: &str = r#" @@ -130,9 +129,9 @@ fn test_get() { #[test] fn test_get_mut() { let mut v = STR_OK.parse::().unwrap(); - let m: &mut HashMap<_, _> = v.get_mut().unwrap(); + let m: &mut JsonMap = v.get_mut().unwrap(); m.clear(); - let m: &HashMap<_, _> = v.get().unwrap(); + let m: &JsonMap = v.get().unwrap(); assert!(m.is_empty()); } @@ -156,10 +155,10 @@ fn test_try_into() { .unwrap(); assert_eq!(&v, &[JsonValue::Null, JsonValue::Number(3.0)]); - let mut m = HashMap::new(); + let mut m = JsonMap::new(); m.insert("a".to_string(), JsonValue::Null); m.insert("b".to_string(), JsonValue::Boolean(true)); - let v: HashMap<_, _> = JsonValue::Object(m.clone()).try_into().unwrap(); + let v: JsonMap = JsonValue::Object(m.clone()).try_into().unwrap(); assert_eq!(v, m); } @@ -182,7 +181,7 @@ fn test_is_xxx() { assert!(Array(vec![]).is_array()); assert!(!Number(1.0).is_array()); - assert!(Object(HashMap::new()).is_object()); + assert!(Object(JsonMap::new()).is_object()); assert!(!Number(1.0).is_object()); } @@ -255,7 +254,7 @@ fn test_from() { assert_eq!(JsonValue::from(()), JsonValue::Null); let v = vec![JsonValue::Number(1.0), JsonValue::Boolean(false)]; assert_eq!(JsonValue::from(v.clone()), JsonValue::Array(v)); - let m: HashMap<_, _> = [ + let m: JsonMap = [ ("a".to_string(), JsonValue::Number(1.0)), ("b".to_string(), JsonValue::Boolean(false)), ]