Repository: mehcode/config-rs (rust)

Issue:
Keep ordering of deserialized maps
Serde's yaml deserialization supports maintaining the order of map types (at least in yaml/json, haven't looked at other formats) by using `LinkedHashMap` instead of `HashMap`. Quickly perusing through `config-rs`, it looks like the `table` value uses `HashMap` and so will always lose the ordering of a map.

AFAICT, a fix to this would be as simple as changing `HashMap` to `LinkedHashMap` in the appropriate places, but since that type is exposed to the API, it could be considered a breaking change. Would the project be receptive to such a change?

Pull request:
Preserve map ordering
Fixes #216 by using [`LinkedHashMap`](https://crates.io/crates/linked-hash-map) instead of `HashMap` to preserve the order of the map types in configs. A few things to note:

- Ordering is not preserved when using `hjson` because the current dependency breaks when using a `LinkedHashMap`. Once that crate [adds support for Serde 1.0](https://github.com/hjson/hjson-rust/pull/24), the feature can be re-enabled and everything should work. There is [an alternative](https://crates.io/crates/deser-hjson) `hjson` serde implementation, but it makes a [design descision](https://github.com/Canop/deser-hjson/tree/1fb13c996764a91334ccf8c4f7ff834bf89be616#why-only-a-derive-based-deserializer) incompatible with use in this project
- Merged configs will be order-preserving within each config -- that is, given the following configs in order:
  ```yaml
  # Config 1
  creator:
    name: John Smith
    username: jsmith
  ```

  ```yaml
  # Config 2
  creator:
    email: jsmith@localhost
    name: John Smith Jr.
  ```

  The resulting config will contain settings in the order found, preserving the order within each individual config only for new settings. Updates to settings is a lower config will have no effect on ordering:
  ```yaml
  # Merged config
  creator:
    name: John Smith Jr.
    username: jsmith
    email: jsmith@localhost
  ```

TODOs:
- [x] Overall, the community seems to be converging on use of [`indexmap`](https://crates.io/crates/indexmap) instead of `LinkedHashMap` for this use case. This is due to some improved performance, less unsafe code, and more active development (`LinkedHashMap` is in maintenance mode). I initially implemented this using `LinkedHashMap` because I wasn't aware of the other crate, but would like to use `index-map` before marking the PR ready
- [x] This current implementation would be a breaking change, but I believe can be made a non-breaking feature by [type aliasing](https://github.com/ron-rs/ron/blob/3984332e191b9eb1f12b2a11fc7dd92cf56387e9/src/value.rs#L129-L132). I would like to also make this change pre-merge
- [x] Check for over-replacement of `HashMap` -> `IndexMap`. I expect there are uses that don't need to be replaced because they don't impact the iteration order of the resulting config, but I haven't yet verified

Patch:
diff --git a/.github/workflows/msrv.yml b/.github/workflows/msrv.yml
index 23e8047..3ec7270 100644
--- a/.github/workflows/msrv.yml
+++ b/.github/workflows/msrv.yml
@@ -63,6 +63,7 @@ jobs:
         uses: actions-rs/cargo@v1
         with:
           command: test
+          args: --all-features
 
       - name: Run cargo test (nightly)
         if: matrix.rust == '1.46.0'
@@ -70,7 +71,7 @@ jobs:
         uses: actions-rs/cargo@v1
         with:
           command: test
-          args: --tests
+          args: --tests --all-features
 
       - name: Run cargo test (nightly)
         if: matrix.rust == 'nightly'
@@ -78,6 +79,7 @@ jobs:
         uses: actions-rs/cargo@v1
         with:
           command: test
+          args: --all-features
 
   fmt:
     needs: [check]
diff --git a/Cargo.toml b/Cargo.toml
index f72389f..fcc1c1e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -21,6 +21,7 @@ yaml = ["yaml-rust"]
 hjson = ["serde-hjson"]
 ini = ["rust-ini"]
 json5 = ["json5_rs"]
+preserve_order = ["indexmap", "toml/preserve_order", "serde_json/preserve_order", "ron/indexmap"]
 
 [dependencies]
 async-trait = "0.1.50"
@@ -35,6 +36,7 @@ serde-hjson = { version = "0.9", default-features = false, optional = true }
 rust-ini = { version = "0.17", optional = true }
 ron = { version = "0.6", optional = true }
 json5_rs = { version = "0.3", optional = true, package = "json5" }
+indexmap = { version = "1.7.0", features = ["serde-1"], optional = true}
 
 [dev-dependencies]
 serde_derive = "1.0.8"
diff --git a/examples/async_source/main.rs b/examples/async_source/main.rs
index 10befe0..005a473 100644
--- a/examples/async_source/main.rs
+++ b/examples/async_source/main.rs
@@ -1,6 +1,6 @@
-use std::{collections::HashMap, error::Error};
+use std::error::Error;
 
-use config::{builder::AsyncState, AsyncSource, ConfigBuilder, ConfigError, FileFormat};
+use config::{builder::AsyncState, AsyncSource, ConfigBuilder, ConfigError, FileFormat, Map};
 
 use async_trait::async_trait;
 use futures::{select, FutureExt};
@@ -56,7 +56,7 @@ struct HttpSource {
 
 #[async_trait]
 impl AsyncSource for HttpSource {
-    async fn collect(&self) -> Result<HashMap<String, config::Value>, ConfigError> {
+    async fn collect(&self) -> Result<Map<String, config::Value>, ConfigError> {
         reqwest::get(&self.uri)
             .await
             .map_err(|e| ConfigError::Foreign(Box::new(e)))? // error conversion is possible from custom AsyncSource impls
diff --git a/examples/glob/src/main.rs b/examples/glob/src/main.rs
index b3183ef..002ddd4 100644
--- a/examples/glob/src/main.rs
+++ b/examples/glob/src/main.rs
@@ -1,5 +1,5 @@
 use std::path::Path;
-use std::collections::HashMap;
+use std::collections::Map;
 use config::*;
 use glob::glob;
 
@@ -14,9 +14,9 @@ fn main() {
         .merge(File::from(Path::new("conf/05-some.yml"))).unwrap()
         .merge(File::from(Path::new("conf/99-extra.json"))).unwrap();
 
-    // Print out our settings (as a HashMap)
+    // Print out our settings (as a Map)
     println!("\n{:?} \n\n-----------",
-             settings.try_into::<HashMap<String, String>>().unwrap());
+             settings.try_into::<Map<String, String>>().unwrap());
 
     // Option 2
     // --------
@@ -28,9 +28,9 @@ fn main() {
                     File::from(Path::new("conf/99-extra.json"))])
         .unwrap();
 
-    // Print out our settings (as a HashMap)
+    // Print out our settings (as a Map)
     println!("\n{:?} \n\n-----------",
-             settings.try_into::<HashMap<String, String>>().unwrap());
+             settings.try_into::<Map<String, String>>().unwrap());
 
     // Option 3
     // --------
@@ -43,7 +43,7 @@ fn main() {
                    .collect::<Vec<_>>())
         .unwrap();
 
-    // Print out our settings (as a HashMap)
+    // Print out our settings (as a Map)
     println!("\n{:?} \n\n-----------",
-             settings.try_into::<HashMap<String, String>>().unwrap());
+             settings.try_into::<Map<String, String>>().unwrap());
 }
diff --git a/examples/simple/src/main.rs b/examples/simple/src/main.rs
index 1c7ddb7..e07eff2 100644
--- a/examples/simple/src/main.rs
+++ b/examples/simple/src/main.rs
@@ -1,4 +1,4 @@
-use std::collections::HashMap;
+use std::collections::Map;
 
 fn main() {
     let mut settings = config::Config::default();
@@ -9,7 +9,7 @@ fn main() {
         // Eg.. `APP_DEBUG=1 ./target/app` would set the `debug` key
         .merge(config::Environment::with_prefix("APP")).unwrap();
 
-    // Print out our settings (as a HashMap)
+    // Print out our settings (as a Map)
     println!("{:?}",
-             settings.try_into::<HashMap<String, String>>().unwrap());
+             settings.try_into::<Map<String, String>>().unwrap());
 }
diff --git a/examples/watch/src/main.rs b/examples/watch/src/main.rs
index a197390..cbe30e7 100644
--- a/examples/watch/src/main.rs
+++ b/examples/watch/src/main.rs
@@ -1,5 +1,5 @@
 use config::*;
-use std::collections::HashMap;
+use std::collections::Map;
 use std::sync::RwLock;
 use notify::{RecommendedWatcher, DebouncedEvent, Watcher, RecursiveMode};
 use std::sync::mpsc::channel;
@@ -20,7 +20,7 @@ fn show() {
                  .read()
                  .unwrap()
                  .clone()
-                 .try_into::<HashMap<String, String>>()
+                 .try_into::<Map<String, String>>()
                  .unwrap());
 }
 
diff --git a/src/builder.rs b/src/builder.rs
index bb88f44..e629606 100644
--- a/src/builder.rs
+++ b/src/builder.rs
@@ -1,7 +1,8 @@
+use std::iter::IntoIterator;
 use std::str::FromStr;
-use std::{collections::HashMap, iter::IntoIterator};
 
 use crate::error::Result;
+use crate::map::Map;
 use crate::source::AsyncSource;
 use crate::{config::Config, path::Expression, source::Source, value::Value};
 
@@ -87,8 +88,8 @@ use crate::{config::Config, path::Expression, source::Source, value::Value};
 /// ```
 #[derive(Debug, Clone, Default)]
 pub struct ConfigBuilder<St: BuilderState> {
-    defaults: HashMap<Expression, Value>,
-    overrides: HashMap<Expression, Value>,
+    defaults: Map<Expression, Value>,
+    overrides: Map<Expression, Value>,
     state: St,
 }
 
@@ -120,8 +121,8 @@ pub struct DefaultState {
 /// Refer to [`ConfigBuilder`] for similar API sample usage or to the examples folder of the crate, where such a source is implemented.
 #[derive(Debug, Clone, Default)]
 pub struct AsyncConfigBuilder {
-    defaults: HashMap<Expression, Value>,
-    overrides: HashMap<Expression, Value>,
+    defaults: Map<Expression, Value>,
+    overrides: Map<Expression, Value>,
     sources: Vec<SourceType>,
 }
 
@@ -244,11 +245,11 @@ impl ConfigBuilder<DefaultState> {
     }
 
     fn build_internal(
-        defaults: HashMap<Expression, Value>,
-        overrides: HashMap<Expression, Value>,
+        defaults: Map<Expression, Value>,
+        overrides: Map<Expression, Value>,
         sources: &[Box<dyn Source + Send + Sync>],
     ) -> Result<Config> {
-        let mut cache: Value = HashMap::<String, Value>::new().into();
+        let mut cache: Value = Map::<String, Value>::new().into();
 
         // Add defaults
         for (key, val) in defaults.into_iter() {
@@ -322,11 +323,11 @@ impl ConfigBuilder<AsyncState> {
     }
 
     async fn build_internal(
-        defaults: HashMap<Expression, Value>,
-        overrides: HashMap<Expression, Value>,
+        defaults: Map<Expression, Value>,
+        overrides: Map<Expression, Value>,
         sources: &[SourceType],
     ) -> Result<Config> {
-        let mut cache: Value = HashMap::<String, Value>::new().into();
+        let mut cache: Value = Map::<String, Value>::new().into();
 
         // Add defaults
         for (key, val) in defaults.into_iter() {
diff --git a/src/config.rs b/src/config.rs
index b9c64b4..55844ce 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,4 +1,3 @@
-use std::collections::HashMap;
 use std::fmt::Debug;
 
 use crate::builder::{ConfigBuilder, DefaultState};
@@ -6,6 +5,7 @@ use serde::de::Deserialize;
 use serde::ser::Serialize;
 
 use crate::error::*;
+use crate::map::Map;
 use crate::path;
 use crate::ser::ConfigSerializer;
 use crate::source::Source;
@@ -16,8 +16,8 @@ use crate::value::{Table, Value};
 /// them according to the source's priority.
 #[derive(Clone, Debug)]
 pub struct Config {
-    defaults: HashMap<path::Expression, Value>,
-    overrides: HashMap<path::Expression, Value>,
+    defaults: Map<path::Expression, Value>,
+    overrides: Map<path::Expression, Value>,
     sources: Vec<Box<dyn Source + Send + Sync>>,
 
     /// Root of the cached configuration.
@@ -83,7 +83,7 @@ impl Config {
     #[deprecated(since = "0.12.0", note = "please use 'ConfigBuilder' instead")]
     pub fn refresh(&mut self) -> Result<&mut Config> {
         self.cache = {
-            let mut cache: Value = HashMap::<String, Value>::new().into();
+            let mut cache: Value = Map::<String, Value>::new().into();
 
             // Add defaults
             for (key, val) in self.defaults.iter() {
@@ -181,7 +181,7 @@ impl Config {
         self.get(key).and_then(Value::into_bool)
     }
 
-    pub fn get_table(&self, key: &str) -> Result<HashMap<String, Value>> {
+    pub fn get_table(&self, key: &str) -> Result<Map<String, Value>> {
         self.get(key).and_then(Value::into_table)
     }
 
@@ -212,7 +212,7 @@ impl Source for Config {
         Box::new((*self).clone())
     }
 
-    fn collect(&self) -> Result<HashMap<String, Value>> {
+    fn collect(&self) -> Result<Map<String, Value>> {
         self.cache.clone().into_table()
     }
 }
diff --git a/src/de.rs b/src/de.rs
index a7a6fb4..60fa4ea 100644
--- a/src/de.rs
+++ b/src/de.rs
@@ -1,10 +1,11 @@
-use std::collections::{HashMap, VecDeque};
+use std::collections::VecDeque;
 use std::iter::Enumerate;
 
 use serde::de;
 
 use crate::config::Config;
 use crate::error::*;
+use crate::map::Map;
 use crate::value::{Table, Value, ValueKind};
 
 impl<'de> de::Deserializer<'de> for Value {
@@ -199,7 +200,7 @@ struct MapAccess {
 }
 
 impl MapAccess {
-    fn new(table: HashMap<String, Value>) -> Self {
+    fn new(table: Map<String, Value>) -> Self {
         MapAccess {
             elements: table.into_iter().collect(),
         }
diff --git a/src/env.rs b/src/env.rs
index 3d95e17..736d584 100644
--- a/src/env.rs
+++ b/src/env.rs
@@ -1,7 +1,7 @@
-use std::collections::HashMap;
 use std::env;
 
 use crate::error::*;
+use crate::map::Map;
 use crate::source::Source;
 use crate::value::{Value, ValueKind};
 
@@ -79,8 +79,8 @@ impl Source for Environment {
         Box::new((*self).clone())
     }
 
-    fn collect(&self) -> Result<HashMap<String, Value>> {
-        let mut m = HashMap::new();
+    fn collect(&self) -> Result<Map<String, Value>> {
+        let mut m = Map::new();
         let uri: String = "the environment".into();
 
         let separator = self.separator.as_deref().unwrap_or("");
diff --git a/src/file/format/hjson.rs b/src/file/format/hjson.rs
index f94b1d3..4b76114 100644
--- a/src/file/format/hjson.rs
+++ b/src/file/format/hjson.rs
@@ -1,19 +1,19 @@
-use std::collections::HashMap;
 use std::error::Error;
 
+use crate::map::Map;
 use crate::value::{Value, ValueKind};
 
 pub fn parse(
     uri: Option<&String>,
     text: &str,
-) -> Result<HashMap<String, Value>, Box<dyn Error + Send + Sync>> {
+) -> Result<Map<String, Value>, Box<dyn Error + Send + Sync>> {
     // Parse a JSON object value from the text
     // TODO: Have a proper error fire if the root of a file is ever not a Table
     let value = from_hjson_value(uri, &serde_hjson::from_str(text)?);
     match value.kind {
         ValueKind::Table(map) => Ok(map),
 
-        _ => Ok(HashMap::new()),
+        _ => Ok(Map::new()),
     }
 }
 
@@ -30,7 +30,7 @@ fn from_hjson_value(uri: Option<&String>, value: &serde_hjson::Value) -> Value {
         serde_hjson::Value::Bool(value) => Value::new(uri, ValueKind::Boolean(value)),
 
         serde_hjson::Value::Object(ref table) => {
-            let mut m = HashMap::new();
+            let mut m = Map::new();
 
             for (key, value) in table {
                 m.insert(key.clone(), from_hjson_value(uri, value));
diff --git a/src/file/format/ini.rs b/src/file/format/ini.rs
index b45695a..9295e60 100644
--- a/src/file/format/ini.rs
+++ b/src/file/format/ini.rs
@@ -1,20 +1,20 @@
-use std::collections::HashMap;
 use std::error::Error;
 
 use ini::Ini;
 
+use crate::map::Map;
 use crate::value::{Value, ValueKind};
 
 pub fn parse(
     uri: Option<&String>,
     text: &str,
-) -> Result<HashMap<String, Value>, Box<dyn Error + Send + Sync>> {
-    let mut map: HashMap<String, Value> = HashMap::new();
+) -> Result<Map<String, Value>, Box<dyn Error + Send + Sync>> {
+    let mut map: Map<String, Value> = Map::new();
     let i = Ini::load_from_str(text)?;
     for (sec, prop) in i.iter() {
         match sec {
             Some(sec) => {
-                let mut sec_map: HashMap<String, Value> = HashMap::new();
+                let mut sec_map: Map<String, Value> = Map::new();
                 for (k, v) in prop.iter() {
                     sec_map.insert(
                         k.to_owned(),
diff --git a/src/file/format/json.rs b/src/file/format/json.rs
index 87a6e61..e3d8b87 100644
--- a/src/file/format/json.rs
+++ b/src/file/format/json.rs
@@ -1,19 +1,19 @@
-use std::collections::HashMap;
 use std::error::Error;
 
+use crate::map::Map;
 use crate::value::{Value, ValueKind};
 
 pub fn parse(
     uri: Option<&String>,
     text: &str,
-) -> Result<HashMap<String, Value>, Box<dyn Error + Send + Sync>> {
+) -> Result<Map<String, Value>, Box<dyn Error + Send + Sync>> {
     // Parse a JSON object value from the text
     // TODO: Have a proper error fire if the root of a file is ever not a Table
     let value = from_json_value(uri, &serde_json::from_str(text)?);
     match value.kind {
         ValueKind::Table(map) => Ok(map),
 
-        _ => Ok(HashMap::new()),
+        _ => Ok(Map::new()),
     }
 }
 
@@ -34,7 +34,7 @@ fn from_json_value(uri: Option<&String>, value: &serde_json::Value) -> Value {
         serde_json::Value::Bool(value) => Value::new(uri, ValueKind::Boolean(value)),
 
         serde_json::Value::Object(ref table) => {
-            let mut m = HashMap::new();
+            let mut m = Map::new();
 
             for (key, value) in table {
                 m.insert(key.clone(), from_json_value(uri, value));
diff --git a/src/file/format/json5.rs b/src/file/format/json5.rs
index ea3390f..c156797 100644
--- a/src/file/format/json5.rs
+++ b/src/file/format/json5.rs
@@ -1,7 +1,7 @@
-use std::collections::HashMap;
 use std::error::Error;
 
 use crate::error::{ConfigError, Unexpected};
+use crate::map::Map;
 use crate::value::{Value, ValueKind};
 
 #[derive(serde::Deserialize, Debug)]
@@ -13,13 +13,13 @@ pub enum Val {
     Float(f64),
     String(String),
     Array(Vec<Val>),
-    Object(HashMap<String, Val>),
+    Object(Map<String, Val>),
 }
 
 pub fn parse(
     uri: Option<&String>,
     text: &str,
-) -> Result<HashMap<String, Value>, Box<dyn Error + Send + Sync>> {
+) -> Result<Map<String, Value>, Box<dyn Error + Send + Sync>> {
     match json5_rs::from_str::<Val>(text)? {
         Val::String(ref value) => Err(Unexpected::Str(value.clone())),
         Val::Integer(value) => Err(Unexpected::Integer(value)),
@@ -29,7 +29,7 @@ pub fn parse(
         Val::Null => Err(Unexpected::Unit),
         Val::Object(o) => match from_json5_value(uri, Val::Object(o)).kind {
             ValueKind::Table(map) => Ok(map),
-            _ => Ok(HashMap::new()),
+            _ => Ok(Map::new()),
         },
     }
     .map_err(|err| ConfigError::invalid_root(uri, err))
diff --git a/src/file/format/mod.rs b/src/file/format/mod.rs
index 53bacf6..9f6d7d7 100644
--- a/src/file/format/mod.rs
+++ b/src/file/format/mod.rs
@@ -5,6 +5,7 @@
 use std::collections::HashMap;
 use std::error::Error;
 
+use crate::map::Map;
 use crate::value::Value;
 
 #[cfg(feature = "toml")]
@@ -107,7 +108,7 @@ impl FileFormat {
         self,
         uri: Option<&String>,
         text: &str,
-    ) -> Result<HashMap<String, Value>, Box<dyn Error + Send + Sync>> {
+    ) -> Result<Map<String, Value>, Box<dyn Error + Send + Sync>> {
         match self {
             #[cfg(feature = "toml")]
             FileFormat::Toml => toml::parse(uri, text),
diff --git a/src/file/format/ron.rs b/src/file/format/ron.rs
index f7dbce3..3fb2a0f 100644
--- a/src/file/format/ron.rs
+++ b/src/file/format/ron.rs
@@ -1,17 +1,17 @@
-use std::collections::HashMap;
 use std::error::Error;
 
+use crate::map::Map;
 use crate::value::{Value, ValueKind};
 
 pub fn parse(
     uri: Option<&String>,
     text: &str,
-) -> Result<HashMap<String, Value>, Box<dyn Error + Send + Sync>> {
+) -> Result<Map<String, Value>, Box<dyn Error + Send + Sync>> {
     let value = from_ron_value(uri, ron::from_str(text)?)?;
     match value.kind {
         ValueKind::Table(map) => Ok(map),
 
-        _ => Ok(HashMap::new()),
+        _ => Ok(Map::new()),
     }
 }
 
@@ -56,7 +56,7 @@ fn from_ron_value(
 
                     Ok((key, value))
                 })
-                .collect::<Result<HashMap<_, _>, _>>()?;
+                .collect::<Result<Map<_, _>, _>>()?;
 
             ValueKind::Table(map)
         }
diff --git a/src/file/format/toml.rs b/src/file/format/toml.rs
index c7c5972..af21fc7 100644
--- a/src/file/format/toml.rs
+++ b/src/file/format/toml.rs
@@ -1,19 +1,19 @@
-use std::collections::HashMap;
 use std::error::Error;
 
+use crate::map::Map;
 use crate::value::{Value, ValueKind};
 
 pub fn parse(
     uri: Option<&String>,
     text: &str,
-) -> Result<HashMap<String, Value>, Box<dyn Error + Send + Sync>> {
+) -> Result<Map<String, Value>, Box<dyn Error + Send + Sync>> {
     // Parse a TOML value from the provided text
     // TODO: Have a proper error fire if the root of a file is ever not a Table
     let value = from_toml_value(uri, &toml::from_str(text)?);
     match value.kind {
         ValueKind::Table(map) => Ok(map),
 
-        _ => Ok(HashMap::new()),
+        _ => Ok(Map::new()),
     }
 }
 
@@ -25,7 +25,7 @@ fn from_toml_value(uri: Option<&String>, value: &toml::Value) -> Value {
         toml::Value::Boolean(value) => Value::new(uri, value),
 
         toml::Value::Table(ref table) => {
-            let mut m = HashMap::new();
+            let mut m = Map::new();
 
             for (key, value) in table {
                 m.insert(key.clone(), from_toml_value(uri, value));
diff --git a/src/file/format/yaml.rs b/src/file/format/yaml.rs
index 2526395..44b72fb 100644
--- a/src/file/format/yaml.rs
+++ b/src/file/format/yaml.rs
@@ -1,16 +1,16 @@
-use std::collections::HashMap;
 use std::error::Error;
 use std::fmt;
 use std::mem;
 
 use yaml_rust as yaml;
 
+use crate::map::Map;
 use crate::value::{Value, ValueKind};
 
 pub fn parse(
     uri: Option<&String>,
     text: &str,
-) -> Result<HashMap<String, Value>, Box<dyn Error + Send + Sync>> {
+) -> Result<Map<String, Value>, Box<dyn Error + Send + Sync>> {
     // Parse a YAML object from file
     let mut docs = yaml::YamlLoader::load_from_str(text)?;
     let root = match docs.len() {
@@ -26,7 +26,7 @@ pub fn parse(
     match value.kind {
         ValueKind::Table(map) => Ok(map),
 
-        _ => Ok(HashMap::new()),
+        _ => Ok(Map::new()),
     }
 }
 
@@ -40,7 +40,7 @@ fn from_yaml_value(uri: Option<&String>, value: &yaml::Yaml) -> Value {
         yaml::Yaml::Integer(value) => Value::new(uri, ValueKind::Integer(value)),
         yaml::Yaml::Boolean(value) => Value::new(uri, ValueKind::Boolean(value)),
         yaml::Yaml::Hash(ref table) => {
-            let mut m = HashMap::new();
+            let mut m = Map::new();
             for (key, value) in table {
                 if let Some(k) = key.as_str() {
                     m.insert(k.to_owned(), from_yaml_value(uri, value));
diff --git a/src/file/mod.rs b/src/file/mod.rs
index b00a271..0160d7c 100644
--- a/src/file/mod.rs
+++ b/src/file/mod.rs
@@ -1,10 +1,10 @@
 mod format;
 pub mod source;
 
-use std::collections::HashMap;
 use std::path::{Path, PathBuf};
 
 use crate::error::*;
+use crate::map::Map;
 use crate::source::Source;
 use crate::value::Value;
 
@@ -99,7 +99,7 @@ where
         Box::new((*self).clone())
     }
 
-    fn collect(&self) -> Result<HashMap<String, Value>> {
+    fn collect(&self) -> Result<Map<String, Value>> {
         // Coerce the file contents to a string
         let (uri, contents, format) = match self
             .source
@@ -110,7 +110,7 @@ where
 
             Err(error) => {
                 if !self.required {
-                    return Ok(HashMap::new());
+                    return Ok(Map::new());
                 }
 
                 return Err(error);
diff --git a/src/lib.rs b/src/lib.rs
index 87c82bc..267e0d8 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -59,6 +59,7 @@ mod de;
 mod env;
 mod error;
 mod file;
+mod map;
 mod path;
 mod ser;
 mod source;
@@ -70,6 +71,7 @@ pub use crate::config::Config;
 pub use crate::env::Environment;
 pub use crate::error::ConfigError;
 pub use crate::file::{File, FileFormat, FileSourceFile, FileSourceString};
+pub use crate::map::Map;
 pub use crate::source::AsyncSource;
 pub use crate::source::Source;
 pub use crate::value::Value;
diff --git a/src/map.rs b/src/map.rs
new file mode 100644
index 0000000..5873f0d
--- /dev/null
+++ b/src/map.rs
@@ -0,0 +1,4 @@
+#[cfg(not(feature = "preserve_order"))]
+pub type Map<K, V> = std::collections::HashMap<K, V>;
+#[cfg(feature = "preserve_order")]
+pub type Map<K, V> = indexmap::IndexMap<K, V>;
diff --git a/src/path/mod.rs b/src/path/mod.rs
index d58a6f2..cd7ccd7 100644
--- a/src/path/mod.rs
+++ b/src/path/mod.rs
@@ -1,7 +1,7 @@
-use std::collections::HashMap;
 use std::str::FromStr;
 
 use crate::error::*;
+use crate::map::Map;
 use crate::value::{Value, ValueKind};
 
 mod parser;
@@ -135,7 +135,7 @@ impl Expression {
                     ),
 
                     _ => {
-                        *value = HashMap::<String, Value>::new().into();
+                        *value = Map::<String, Value>::new().into();
 
                         if let ValueKind::Table(ref mut map) = value.kind {
                             Some(
@@ -186,7 +186,7 @@ impl Expression {
                     ValueKind::Table(_) => {}
 
                     _ => {
-                        *root = HashMap::<String, Value>::new().into();
+                        *root = Map::<String, Value>::new().into();
                     }
                 }
 
@@ -195,7 +195,7 @@ impl Expression {
                         // Pull out another table
                         let mut target = if let ValueKind::Table(ref mut map) = root.kind {
                             map.entry(id.clone())
-                                .or_insert_with(|| HashMap::<String, Value>::new().into())
+                                .or_insert_with(|| Map::<String, Value>::new().into())
                         } else {
                             unreachable!();
                         };
@@ -209,7 +209,11 @@ impl Expression {
                     _ => {
                         if let ValueKind::Table(ref mut map) = root.kind {
                             // Just do a simple set
-                            map.insert(id.clone(), value);
+                            if let Some(existing) = map.get_mut(id) {
+                                *existing = value;
+                            } else {
+                                map.insert(id.clone(), value);
+                            }
                         }
                     }
                 }
@@ -224,7 +228,7 @@ impl Expression {
 
                         _ => {
                             // Didn't find a table. Oh well. Make a table and do this anyway
-                            *parent = HashMap::<String, Value>::new().into();
+                            *parent = Map::<String, Value>::new().into();
 
                             Expression::Identifier(key.clone()).set(parent, value);
                         }
diff --git a/src/source.rs b/src/source.rs
index 831e4c4..0faf6d1 100644
--- a/src/source.rs
+++ b/src/source.rs
@@ -1,10 +1,10 @@
-use std::collections::HashMap;
 use std::fmt::Debug;
 use std::str::FromStr;
 
 use async_trait::async_trait;
 
 use crate::error::*;
+use crate::map::Map;
 use crate::path;
 use crate::value::{Value, ValueKind};
 
@@ -13,8 +13,8 @@ pub trait Source: Debug {
     fn clone_into_box(&self) -> Box<dyn Source + Send + Sync>;
 
     /// Collect all configuration properties available from this source and return
-    /// a HashMap.
-    fn collect(&self) -> Result<HashMap<String, Value>>;
+    /// a Map.
+    fn collect(&self) -> Result<Map<String, Value>>;
 
     /// Collects all configuration properties to a provided cache.
     fn collect_to(&self, cache: &mut Value) -> Result<()> {
@@ -55,8 +55,8 @@ pub trait AsyncSource: Debug + Sync {
     // Sync is supertrait due to https://docs.rs/async-trait/0.1.50/async_trait/index.html#dyn-traits
 
     /// Collects all configuration properties available from this source and return
-    /// a HashMap as an async operations.
-    async fn collect(&self) -> Result<HashMap<String, Value>>;
+    /// a Map as an async operations.
+    async fn collect(&self) -> Result<Map<String, Value>>;
 
     /// Collects all configuration properties to a provided cache.
     async fn collect_to(&self, cache: &mut Value) -> Result<()> {
@@ -86,8 +86,8 @@ impl Source for Vec<Box<dyn Source + Send + Sync>> {
         Box::new((*self).clone())
     }
 
-    fn collect(&self) -> Result<HashMap<String, Value>> {
-        let mut cache: Value = HashMap::<String, Value>::new().into();
+    fn collect(&self) -> Result<Map<String, Value>> {
+        let mut cache: Value = Map::<String, Value>::new().into();
 
         for source in self {
             source.collect_to(&mut cache)?;
@@ -106,8 +106,8 @@ impl Source for [Box<dyn Source + Send + Sync>] {
         Box::new(self.to_owned())
     }
 
-    fn collect(&self) -> Result<HashMap<String, Value>> {
-        let mut cache: Value = HashMap::<String, Value>::new().into();
+    fn collect(&self) -> Result<Map<String, Value>> {
+        let mut cache: Value = Map::<String, Value>::new().into();
 
         for source in self {
             source.collect_to(&mut cache)?;
@@ -131,8 +131,8 @@ where
         Box::new((*self).clone())
     }
 
-    fn collect(&self) -> Result<HashMap<String, Value>> {
-        let mut cache: Value = HashMap::<String, Value>::new().into();
+    fn collect(&self) -> Result<Map<String, Value>> {
+        let mut cache: Value = Map::<String, Value>::new().into();
 
         for source in self {
             source.collect_to(&mut cache)?;
diff --git a/src/value.rs b/src/value.rs
index 8db5fcc..1523c1e 100644
--- a/src/value.rs
+++ b/src/value.rs
@@ -1,10 +1,10 @@
-use std::collections::HashMap;
 use std::fmt;
 use std::fmt::Display;
 
 use serde::de::{Deserialize, Deserializer, Visitor};
 
 use crate::error::*;
+use crate::map::Map;
 
 /// Underlying kind of the configuration value.
 ///
@@ -23,7 +23,7 @@ pub enum ValueKind {
 }
 
 pub type Array = Vec<Value>;
-pub type Table = HashMap<String, Value>;
+pub type Table = Map<String, Value>;
 
 impl Default for ValueKind {
     fn default() -> Self {
@@ -73,11 +73,11 @@ impl From<bool> for ValueKind {
     }
 }
 
-impl<T> From<HashMap<String, T>> for ValueKind
+impl<T> From<Map<String, T>> for ValueKind
 where
     T: Into<Value>,
 {
-    fn from(values: HashMap<String, T>) -> Self {
+    fn from(values: Map<String, T>) -> Self {
         let t = values.into_iter().map(|(k, v)| (k, v.into())).collect();
         ValueKind::Table(t)
     }
@@ -357,7 +357,7 @@ impl Value {
 
     /// If the `Value` is a Table, returns the associated Map.
     // FIXME: Should this not be `try_into_*` ?
-    pub fn into_table(self) -> Result<HashMap<String, Value>> {
+    pub fn into_table(self) -> Result<Map<String, Value>> {
         match self.kind {
             ValueKind::Table(value) => Ok(value),
 


Test patch:
diff --git a/tests/Settings.hjson b/tests/Settings.hjson
index 3e04ccf..9810e04 100644
--- a/tests/Settings.hjson
+++ b/tests/Settings.hjson
@@ -11,6 +11,8 @@
     rating: 4.5
     creator: {
       name: John Smith
+      username: jsmith
+      email: jsmith@localhost
     }
   }
 }
diff --git a/tests/Settings.json b/tests/Settings.json
index 001948d..babb0ba 100644
--- a/tests/Settings.json
+++ b/tests/Settings.json
@@ -11,7 +11,9 @@
     "reviews": 3866,
     "rating": 4.5,
     "creator": {
-      "name": "John Smith"
+      "name": "John Smith",
+      "username": "jsmith",
+      "email": "jsmith@localhost"
     }
   }
 }
diff --git a/tests/Settings.json5 b/tests/Settings.json5
index cfab1f5..4c93e42 100644
--- a/tests/Settings.json5
+++ b/tests/Settings.json5
@@ -13,6 +13,8 @@
     rating: 4.5,
     creator: {
       name: "John Smith",
+      "username": "jsmith",
+      "email": "jsmith@localhost",
     }
   }
 }
diff --git a/tests/Settings.ron b/tests/Settings.ron
index 528fd61..7882840 100644
--- a/tests/Settings.ron
+++ b/tests/Settings.ron
@@ -12,7 +12,9 @@
     rating: Some(4.5),
     telephone: None,
     creator: {
-      "name": "John Smith"
+      "name": "John Smith",
+      "username": "jsmith",
+      "email": "jsmith@localhost"
     }
   )
 )
diff --git a/tests/Settings.toml b/tests/Settings.toml
index bafb319..bb53808 100644
--- a/tests/Settings.toml
+++ b/tests/Settings.toml
@@ -41,6 +41,8 @@ rating = 4.5
 
 [place.creator]
 name = "John Smith"
+username = "jsmith"
+email = "jsmith@localhost"
 
 [proton]
 up = 2
diff --git a/tests/Settings.yaml b/tests/Settings.yaml
index c92fcb0..8e79c29 100644
--- a/tests/Settings.yaml
+++ b/tests/Settings.yaml
@@ -10,3 +10,5 @@ place:
   rating: 4.5
   creator:
     name: John Smith
+    username: jsmith
+    email: jsmith@localhost
diff --git a/tests/async_builder.rs b/tests/async_builder.rs
index 5d7d6ba..b0aa0f4 100644
--- a/tests/async_builder.rs
+++ b/tests/async_builder.rs
@@ -18,7 +18,7 @@ impl AsyncFile {
 
 #[async_trait]
 impl AsyncSource for AsyncFile {
-    async fn collect(&self) -> Result<std::collections::HashMap<String, Value>, ConfigError> {
+    async fn collect(&self) -> Result<Map<String, Value>, ConfigError> {
         let mut path = env::current_dir().unwrap();
         let local = path::PathBuf::from_str(&self.path).unwrap();
 
diff --git a/tests/errors.rs b/tests/errors.rs
index e11ad69..60f1121 100644
--- a/tests/errors.rs
+++ b/tests/errors.rs
@@ -96,7 +96,7 @@ fn test_error_enum_de() {
     ]
     .iter()
     .cloned()
-    .collect::<std::collections::HashMap<String, Value>>()
+    .collect::<Map<String, Value>>()
     .into();
     let confused_d = confused_v.try_into::<Diode>();
     assert_eq!(
diff --git a/tests/file_hjson.rs b/tests/file_hjson.rs
index 4002a90..4ac2e12 100644
--- a/tests/file_hjson.rs
+++ b/tests/file_hjson.rs
@@ -7,7 +7,6 @@ extern crate serde;
 #[macro_use]
 extern crate serde_derive;
 
-use std::collections::HashMap;
 use std::path::PathBuf;
 
 use config::*;
@@ -21,7 +20,7 @@ struct Place {
     favorite: bool,
     telephone: Option<String>,
     reviews: u64,
-    creator: HashMap<String, Value>,
+    creator: Map<String, Value>,
     rating: Option<f32>,
 }
 
diff --git a/tests/file_json.rs b/tests/file_json.rs
index 4563e42..d3fa0a9 100644
--- a/tests/file_json.rs
+++ b/tests/file_json.rs
@@ -7,7 +7,6 @@ extern crate serde;
 #[macro_use]
 extern crate serde_derive;
 
-use std::collections::HashMap;
 use std::path::PathBuf;
 
 use config::*;
@@ -21,7 +20,7 @@ struct Place {
     favorite: bool,
     telephone: Option<String>,
     reviews: u64,
-    creator: HashMap<String, Value>,
+    creator: Map<String, Value>,
     rating: Option<f32>,
 }
 
@@ -59,10 +58,24 @@ fn test_file() {
     assert_eq!(s.place.telephone, None);
     assert_eq!(s.elements.len(), 10);
     assert_eq!(s.elements[3], "4".to_string());
-    assert_eq!(
-        s.place.creator["name"].clone().into_string().unwrap(),
-        "John Smith".to_string()
-    );
+    if cfg!(feature = "preserve_order") {
+        assert_eq!(
+            s.place
+                .creator
+                .into_iter()
+                .collect::<Vec<(String, config::Value)>>(),
+            vec![
+                ("name".to_string(), "John Smith".into()),
+                ("username".into(), "jsmith".into()),
+                ("email".into(), "jsmith@localhost".into()),
+            ]
+        );
+    } else {
+        assert_eq!(
+            s.place.creator["name"].clone().into_string().unwrap(),
+            "John Smith".to_string()
+        );
+    }
 }
 
 #[test]
diff --git a/tests/file_json5.rs b/tests/file_json5.rs
index d22b726..a768bba 100644
--- a/tests/file_json5.rs
+++ b/tests/file_json5.rs
@@ -9,7 +9,6 @@ extern crate serde_derive;
 
 use config::*;
 use float_cmp::ApproxEqUlps;
-use std::collections::HashMap;
 use std::path::PathBuf;
 
 #[derive(Debug, Deserialize)]
@@ -20,7 +19,7 @@ struct Place {
     favorite: bool,
     telephone: Option<String>,
     reviews: u64,
-    creator: HashMap<String, Value>,
+    creator: Map<String, Value>,
     rating: Option<f32>,
 }
 
@@ -58,10 +57,24 @@ fn test_file() {
     assert_eq!(s.place.telephone, None);
     assert_eq!(s.elements.len(), 10);
     assert_eq!(s.elements[3], "4".to_string());
-    assert_eq!(
-        s.place.creator["name"].clone().into_string().unwrap(),
-        "John Smith".to_string()
-    );
+    if cfg!(feature = "preserve_order") {
+        assert_eq!(
+            s.place
+                .creator
+                .into_iter()
+                .collect::<Vec<(String, config::Value)>>(),
+            vec![
+                ("name".to_string(), "John Smith".into()),
+                ("username".into(), "jsmith".into()),
+                ("email".into(), "jsmith@localhost".into()),
+            ]
+        );
+    } else {
+        assert_eq!(
+            s.place.creator["name"].clone().into_string().unwrap(),
+            "John Smith".to_string()
+        );
+    }
 }
 
 #[test]
diff --git a/tests/file_ron.rs b/tests/file_ron.rs
index d60c890..32c7192 100644
--- a/tests/file_ron.rs
+++ b/tests/file_ron.rs
@@ -7,7 +7,6 @@ extern crate serde;
 #[macro_use]
 extern crate serde_derive;
 
-use std::collections::HashMap;
 use std::path::PathBuf;
 
 use config::*;
@@ -22,7 +21,7 @@ struct Place {
     favorite: bool,
     telephone: Option<String>,
     reviews: u64,
-    creator: HashMap<String, Value>,
+    creator: Map<String, Value>,
     rating: Option<f32>,
 }
 
@@ -61,10 +60,24 @@ fn test_file() {
     assert_eq!(s.place.telephone, None);
     assert_eq!(s.elements.len(), 10);
     assert_eq!(s.elements[3], "4".to_string());
-    assert_eq!(
-        s.place.creator["name"].clone().into_string().unwrap(),
-        "John Smith".to_string()
-    );
+    if cfg!(feature = "preserve_order") {
+        assert_eq!(
+            s.place
+                .creator
+                .into_iter()
+                .collect::<Vec<(String, config::Value)>>(),
+            vec![
+                ("name".to_string(), "John Smith".into()),
+                ("username".into(), "jsmith".into()),
+                ("email".into(), "jsmith@localhost".into()),
+            ]
+        );
+    } else {
+        assert_eq!(
+            s.place.creator["name"].clone().into_string().unwrap(),
+            "John Smith".to_string()
+        );
+    }
 }
 
 #[test]
diff --git a/tests/file_toml.rs b/tests/file_toml.rs
index 9493bbd..b6bb537 100644
--- a/tests/file_toml.rs
+++ b/tests/file_toml.rs
@@ -7,7 +7,6 @@ extern crate serde;
 #[macro_use]
 extern crate serde_derive;
 
-use std::collections::HashMap;
 use std::path::PathBuf;
 
 use config::*;
@@ -22,7 +21,7 @@ struct Place {
     favorite: bool,
     telephone: Option<String>,
     reviews: u64,
-    creator: HashMap<String, Value>,
+    creator: Map<String, Value>,
     rating: Option<f32>,
 }
 
@@ -70,10 +69,24 @@ fn test_file() {
     assert_eq!(s.place.telephone, None);
     assert_eq!(s.elements.len(), 10);
     assert_eq!(s.elements[3], "4".to_string());
-    assert_eq!(
-        s.place.creator["name"].clone().into_string().unwrap(),
-        "John Smith".to_string()
-    );
+    if cfg!(feature = "preserve_order") {
+        assert_eq!(
+            s.place
+                .creator
+                .into_iter()
+                .collect::<Vec<(String, config::Value)>>(),
+            vec![
+                ("name".to_string(), "John Smith".into()),
+                ("username".into(), "jsmith".into()),
+                ("email".into(), "jsmith@localhost".into()),
+            ]
+        );
+    } else {
+        assert_eq!(
+            s.place.creator["name"].clone().into_string().unwrap(),
+            "John Smith".to_string()
+        );
+    }
 }
 
 #[test]
diff --git a/tests/file_yaml.rs b/tests/file_yaml.rs
index 90feefc..c87d5e9 100644
--- a/tests/file_yaml.rs
+++ b/tests/file_yaml.rs
@@ -7,7 +7,6 @@ extern crate serde;
 #[macro_use]
 extern crate serde_derive;
 
-use std::collections::HashMap;
 use std::path::PathBuf;
 
 use config::*;
@@ -21,7 +20,7 @@ struct Place {
     favorite: bool,
     telephone: Option<String>,
     reviews: u64,
-    creator: HashMap<String, Value>,
+    creator: Map<String, Value>,
     rating: Option<f32>,
 }
 
@@ -59,10 +58,24 @@ fn test_file() {
     assert_eq!(s.place.telephone, None);
     assert_eq!(s.elements.len(), 10);
     assert_eq!(s.elements[3], "4".to_string());
-    assert_eq!(
-        s.place.creator["name"].clone().into_string().unwrap(),
-        "John Smith".to_string()
-    );
+    if cfg!(feature = "preserve_order") {
+        assert_eq!(
+            s.place
+                .creator
+                .into_iter()
+                .collect::<Vec<(String, config::Value)>>(),
+            vec![
+                ("name".to_string(), "John Smith".into()),
+                ("username".into(), "jsmith".into()),
+                ("email".into(), "jsmith@localhost".into()),
+            ]
+        );
+    } else {
+        assert_eq!(
+            s.place.creator["name"].clone().into_string().unwrap(),
+            "John Smith".to_string()
+        );
+    }
 }
 
 #[test]
diff --git a/tests/get.rs b/tests/get.rs
index c6030ae..cc9befb 100644
--- a/tests/get.rs
+++ b/tests/get.rs
@@ -7,7 +7,7 @@ extern crate serde;
 #[macro_use]
 extern crate serde_derive;
 
-use std::collections::{HashMap, HashSet};
+use std::collections::HashSet;
 
 use config::*;
 use float_cmp::ApproxEqUlps;
@@ -107,7 +107,7 @@ fn test_get_scalar_path_subscript() {
 #[test]
 fn test_map() {
     let c = make();
-    let m: HashMap<String, Value> = c.get("place").unwrap();
+    let m: Map<String, Value> = c.get("place").unwrap();
 
     assert_eq!(m.len(), 8);
     assert_eq!(
@@ -120,17 +120,28 @@ fn test_map() {
 #[test]
 fn test_map_str() {
     let c = make();
-    let m: HashMap<String, String> = c.get("place.creator").unwrap();
-
-    assert_eq!(m.len(), 1);
-    assert_eq!(m["name"], "John Smith".to_string());
+    let m: Map<String, String> = c.get("place.creator").unwrap();
+
+    if cfg!(feature = "preserve_order") {
+        assert_eq!(
+            m.into_iter().collect::<Vec<(String, String)>>(),
+            vec![
+                ("name".to_string(), "John Smith".to_string()),
+                ("username".to_string(), "jsmith".to_string()),
+                ("email".to_string(), "jsmith@localhost".to_string()),
+            ]
+        );
+    } else {
+        assert_eq!(m.len(), 3);
+        assert_eq!(m["name"], "John Smith".to_string());
+    }
 }
 
 #[test]
 fn test_map_struct() {
     #[derive(Debug, Deserialize)]
     struct Settings {
-        place: HashMap<String, Value>,
+        place: Map<String, Value>,
     }
 
     let c = make();
@@ -215,7 +226,7 @@ fn test_enum() {
     }
     #[derive(Debug, Deserialize)]
     struct Settings {
-        diodes: HashMap<String, Diode>,
+        diodes: Map<String, Diode>,
     }
 
     let c = make();
@@ -248,7 +259,7 @@ fn test_enum_key() {
 
     #[derive(Debug, Deserialize)]
     struct Settings {
-        proton: HashMap<Quark, usize>,
+        proton: Map<Quark, usize>,
         // Just to make sure that set keys work too.
         quarks: HashSet<Quark>,
     }
@@ -264,7 +275,7 @@ fn test_enum_key() {
 fn test_int_key() {
     #[derive(Debug, Deserialize, PartialEq)]
     struct Settings {
-        divisors: HashMap<u32, u32>,
+        divisors: Map<u32, u32>,
     }
 
     let c = make();
diff --git a/tests/legacy/errors.rs b/tests/legacy/errors.rs
index 9058c80..c0ce234 100644
--- a/tests/legacy/errors.rs
+++ b/tests/legacy/errors.rs
@@ -93,7 +93,7 @@ fn test_error_enum_de() {
     ]
     .iter()
     .cloned()
-    .collect::<std::collections::HashMap<String, Value>>()
+    .collect::<Map<String, Value>>()
     .into();
     let confused_d = confused_v.try_into::<Diode>();
     assert_eq!(
diff --git a/tests/legacy/file_hjson.rs b/tests/legacy/file_hjson.rs
index 120bcc6..8801a13 100644
--- a/tests/legacy/file_hjson.rs
+++ b/tests/legacy/file_hjson.rs
@@ -4,7 +4,6 @@ extern crate config;
 extern crate float_cmp;
 extern crate serde;
 
-use std::collections::HashMap;
 use std::path::PathBuf;
 
 use self::config::*;
@@ -18,7 +17,7 @@ struct Place {
     favorite: bool,
     telephone: Option<String>,
     reviews: u64,
-    creator: HashMap<String, Value>,
+    creator: Map<String, Value>,
     rating: Option<f32>,
 }
 
diff --git a/tests/legacy/file_json.rs b/tests/legacy/file_json.rs
index e2300c6..b2294b0 100644
--- a/tests/legacy/file_json.rs
+++ b/tests/legacy/file_json.rs
@@ -4,7 +4,6 @@ extern crate config;
 extern crate float_cmp;
 extern crate serde;
 
-use std::collections::HashMap;
 use std::path::PathBuf;
 
 use self::config::*;
@@ -18,7 +17,7 @@ struct Place {
     favorite: bool,
     telephone: Option<String>,
     reviews: u64,
-    creator: HashMap<String, Value>,
+    creator: Map<String, Value>,
     rating: Option<f32>,
 }
 
@@ -57,10 +56,24 @@ fn test_file() {
     assert_eq!(s.place.telephone, None);
     assert_eq!(s.elements.len(), 10);
     assert_eq!(s.elements[3], "4".to_string());
-    assert_eq!(
-        s.place.creator["name"].clone().into_string().unwrap(),
-        "John Smith".to_string()
-    );
+    if cfg!(feature = "preserve_order") {
+        assert_eq!(
+            s.place
+                .creator
+                .into_iter()
+                .collect::<Vec<(String, config::Value)>>(),
+            vec![
+                ("name".to_string(), "John Smith".into()),
+                ("username".into(), "jsmith".into()),
+                ("email".into(), "jsmith@localhost".into()),
+            ]
+        );
+    } else {
+        assert_eq!(
+            s.place.creator["name"].clone().into_string().unwrap(),
+            "John Smith".to_string()
+        );
+    }
 }
 
 #[test]
diff --git a/tests/legacy/file_ron.rs b/tests/legacy/file_ron.rs
index 91f88f3..ea8bc27 100644
--- a/tests/legacy/file_ron.rs
+++ b/tests/legacy/file_ron.rs
@@ -4,7 +4,6 @@ extern crate config;
 extern crate float_cmp;
 extern crate serde;
 
-use std::collections::HashMap;
 use std::path::PathBuf;
 
 use self::config::*;
@@ -19,7 +18,7 @@ struct Place {
     favorite: bool,
     telephone: Option<String>,
     reviews: u64,
-    creator: HashMap<String, Value>,
+    creator: Map<String, Value>,
     rating: Option<f32>,
 }
 
@@ -59,10 +58,24 @@ fn test_file() {
     assert_eq!(s.place.telephone, None);
     assert_eq!(s.elements.len(), 10);
     assert_eq!(s.elements[3], "4".to_string());
-    assert_eq!(
-        s.place.creator["name"].clone().into_string().unwrap(),
-        "John Smith".to_string()
-    );
+    if cfg!(feature = "preserve_order") {
+        assert_eq!(
+            s.place
+                .creator
+                .into_iter()
+                .collect::<Vec<(String, config::Value)>>(),
+            vec![
+                ("name".to_string(), "John Smith".into()),
+                ("username".into(), "jsmith".into()),
+                ("email".into(), "jsmith@localhost".into()),
+            ]
+        );
+    } else {
+        assert_eq!(
+            s.place.creator["name"].clone().into_string().unwrap(),
+            "John Smith".to_string()
+        );
+    }
 }
 
 #[test]
diff --git a/tests/legacy/file_toml.rs b/tests/legacy/file_toml.rs
index 2271962..b8a1337 100644
--- a/tests/legacy/file_toml.rs
+++ b/tests/legacy/file_toml.rs
@@ -4,7 +4,6 @@ extern crate config;
 extern crate float_cmp;
 extern crate serde;
 
-use std::collections::HashMap;
 use std::path::PathBuf;
 
 use self::config::*;
@@ -19,7 +18,7 @@ struct Place {
     favorite: bool,
     telephone: Option<String>,
     reviews: u64,
-    creator: HashMap<String, Value>,
+    creator: Map<String, Value>,
     rating: Option<f32>,
 }
 
@@ -68,10 +67,24 @@ fn test_file() {
     assert_eq!(s.place.telephone, None);
     assert_eq!(s.elements.len(), 10);
     assert_eq!(s.elements[3], "4".to_string());
-    assert_eq!(
-        s.place.creator["name"].clone().into_string().unwrap(),
-        "John Smith".to_string()
-    );
+    if cfg!(feature = "preserve_order") {
+        assert_eq!(
+            s.place
+                .creator
+                .into_iter()
+                .collect::<Vec<(String, config::Value)>>(),
+            vec![
+                ("name".to_string(), "John Smith".into()),
+                ("username".into(), "jsmith".into()),
+                ("email".into(), "jsmith@localhost".into()),
+            ]
+        );
+    } else {
+        assert_eq!(
+            s.place.creator["name"].clone().into_string().unwrap(),
+            "John Smith".to_string()
+        );
+    }
 }
 
 #[test]
diff --git a/tests/legacy/file_yaml.rs b/tests/legacy/file_yaml.rs
index 5d7a60f..818518b 100644
--- a/tests/legacy/file_yaml.rs
+++ b/tests/legacy/file_yaml.rs
@@ -4,7 +4,6 @@ extern crate config;
 extern crate float_cmp;
 extern crate serde;
 
-use std::collections::HashMap;
 use std::path::PathBuf;
 
 use self::config::*;
@@ -18,7 +17,7 @@ struct Place {
     favorite: bool,
     telephone: Option<String>,
     reviews: u64,
-    creator: HashMap<String, Value>,
+    creator: Map<String, Value>,
     rating: Option<f32>,
 }
 
@@ -57,10 +56,24 @@ fn test_file() {
     assert_eq!(s.place.telephone, None);
     assert_eq!(s.elements.len(), 10);
     assert_eq!(s.elements[3], "4".to_string());
-    assert_eq!(
-        s.place.creator["name"].clone().into_string().unwrap(),
-        "John Smith".to_string()
-    );
+    if cfg!(feature = "preserve_order") {
+        assert_eq!(
+            s.place
+                .creator
+                .into_iter()
+                .collect::<Vec<(String, config::Value)>>(),
+            vec![
+                ("name".to_string(), "John Smith".into()),
+                ("username".into(), "jsmith".into()),
+                ("email".into(), "jsmith@localhost".into()),
+            ]
+        );
+    } else {
+        assert_eq!(
+            s.place.creator["name"].clone().into_string().unwrap(),
+            "John Smith".to_string()
+        );
+    }
 }
 
 #[test]
diff --git a/tests/legacy/get.rs b/tests/legacy/get.rs
index 48a7b43..061e35e 100644
--- a/tests/legacy/get.rs
+++ b/tests/legacy/get.rs
@@ -4,7 +4,7 @@ extern crate config;
 extern crate float_cmp;
 extern crate serde;
 
-use std::collections::{HashMap, HashSet};
+use std::collections::HashSet;
 
 use self::config::*;
 use self::float_cmp::ApproxEqUlps;
@@ -105,7 +105,7 @@ fn test_get_scalar_path_subscript() {
 #[test]
 fn test_map() {
     let c = make();
-    let m: HashMap<String, Value> = c.get("place").unwrap();
+    let m: Map<String, Value> = c.get("place").unwrap();
 
     assert_eq!(m.len(), 8);
     assert_eq!(
@@ -118,17 +118,28 @@ fn test_map() {
 #[test]
 fn test_map_str() {
     let c = make();
-    let m: HashMap<String, String> = c.get("place.creator").unwrap();
-
-    assert_eq!(m.len(), 1);
-    assert_eq!(m["name"], "John Smith".to_string());
+    let m: Map<String, String> = c.get("place.creator").unwrap();
+
+    if cfg!(feature = "preserve_order") {
+        assert_eq!(
+            m.into_iter().collect::<Vec<(String, String)>>(),
+            vec![
+                ("name".to_string(), "John Smith".to_string()),
+                ("username".to_string(), "jsmith".to_string()),
+                ("email".to_string(), "jsmith@localhost".to_string()),
+            ]
+        );
+    } else {
+        assert_eq!(m.len(), 3);
+        assert_eq!(m["name"], "John Smith".to_string());
+    }
 }
 
 #[test]
 fn test_map_struct() {
     #[derive(Debug, Deserialize)]
     struct Settings {
-        place: HashMap<String, Value>,
+        place: Map<String, Value>,
     }
 
     let c = make();
@@ -213,7 +224,7 @@ fn test_enum() {
     }
     #[derive(Debug, Deserialize)]
     struct Settings {
-        diodes: HashMap<String, Diode>,
+        diodes: Map<String, Diode>,
     }
 
     let c = make();
@@ -246,7 +257,7 @@ fn test_enum_key() {
 
     #[derive(Debug, Deserialize)]
     struct Settings {
-        proton: HashMap<Quark, usize>,
+        proton: Map<Quark, usize>,
         // Just to make sure that set keys work too.
         quarks: HashSet<Quark>,
     }
@@ -262,7 +273,7 @@ fn test_enum_key() {
 fn test_int_key() {
     #[derive(Debug, Deserialize, PartialEq)]
     struct Settings {
-        divisors: HashMap<u32, u32>,
+        divisors: Map<u32, u32>,
     }
 
     let c = make();
diff --git a/tests/legacy/merge.rs b/tests/legacy/merge.rs
index 989ae47..8f70971 100644
--- a/tests/legacy/merge.rs
+++ b/tests/legacy/merge.rs
@@ -21,11 +21,24 @@ fn test_merge() {
 
     assert_eq!(c.get("debug").ok(), Some(false));
     assert_eq!(c.get("production").ok(), Some(true));
-    assert_eq!(
-        c.get("place.creator.name").ok(),
-        Some("Somebody New".to_string())
-    );
     assert_eq!(c.get("place.rating").ok(), Some(4.9));
+
+    if cfg!(feature = "preserve_order") {
+        let m: Map<String, String> = c.get("place.creator").unwrap();
+        assert_eq!(
+            m.into_iter().collect::<Vec<(String, String)>>(),
+            vec![
+                ("name".to_string(), "Somebody New".to_string()),
+                ("username".to_string(), "jsmith".to_string()),
+                ("email".to_string(), "jsmith@localhost".to_string()),
+            ]
+        );
+    } else {
+        assert_eq!(
+            c.get("place.creator.name").ok(),
+            Some("Somebody New".to_string())
+        );
+    }
 }
 
 #[test]
diff --git a/tests/merge.rs b/tests/merge.rs
index 9de36b2..cbf2993 100644
--- a/tests/merge.rs
+++ b/tests/merge.rs
@@ -18,11 +18,24 @@ fn test_merge() {
 
     assert_eq!(c.get("debug").ok(), Some(false));
     assert_eq!(c.get("production").ok(), Some(true));
-    assert_eq!(
-        c.get("place.creator.name").ok(),
-        Some("Somebody New".to_string())
-    );
     assert_eq!(c.get("place.rating").ok(), Some(4.9));
+
+    if cfg!(feature = "preserve_order") {
+        let m: Map<String, String> = c.get("place.creator").unwrap();
+        assert_eq!(
+            m.into_iter().collect::<Vec<(String, String)>>(),
+            vec![
+                ("name".to_string(), "Somebody New".to_string()),
+                ("username".to_string(), "jsmith".to_string()),
+                ("email".to_string(), "jsmith@localhost".to_string()),
+            ]
+        );
+    } else {
+        assert_eq!(
+            c.get("place.creator.name").ok(),
+            Some("Somebody New".to_string())
+        );
+    }
 }
 
 #[test]