Repository: rust-cli/config-rs (language: rust)

Issue:
Reading HashMap from empty json fails with type error.
version: 0.15.4

Config fails to initialize (an empty) HashMap from an empty object.

Repro: 
```
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Config {
    pub data: HashMap<String, String>,
}
...
 let cfg = config::Config::builder()
            .add_source(config::File::from_str(
                //r#"{"data": {"a": "b"}}"#,  //  it works fine
                r#"{"data": {}}"#,                // it fails
                config::FileFormat::Json,
            ))
            .build()
            .unwrap();
        let cfg = cfg.try_deserialize::<Config>();
        log::info!("configuration: {:#?}", cfg);
```

Expected behavior: data is initialized to an empty HashMap.
Actual result: `Err( invalid type: unit value, expected a map )`



Discussion / hints:
This seems to happen every time a value is initialized from an empty `HashMap`, including as a `set_default`

```rust
use config::Config;
use std::collections::HashMap;

fn main() {
    let config = Config::builder().set_default("var", HashMap::<String, i64>::new()).unwrap().build().unwrap();
    // returns empty HashMap on 0.14.*/0.15.2, Errs on "invalid type: unit value, expected a map" on 0.15.3/0.15.4
    let val : HashMap<String, i64> = config.get("var").unwrap();
    println!("Hello, world! {:?}", val);
}
```
I'm stumbling over this as well. Also happens with structs where all fields are `Option`.
Encountered this issue as well. Appears to be a regression, it worked with 0.15.2, but fails with 0.15.3 and 0.15.4.
`git bisect` identified ec36bff45eb339077e23d47d2edabc0bcd03cf6c as the commit that introduced this issue.

Fix patch:
diff --git a/src/path/mod.rs b/src/path/mod.rs
index 333a4d57..f02194c8 100644
--- a/src/path/mod.rs
+++ b/src/path/mod.rs
@@ -150,12 +150,17 @@ impl Expression {
         let parent = self.get_mut_forcibly(root);
         match value.kind {
             ValueKind::Table(ref incoming_map) => {
+                // If the parent is not a table, overwrite it, treating it as a
+                // table
+                if !matches!(parent.kind, ValueKind::Table(_)) {
+                    *parent = Map::<String, Value>::new().into();
+                }
+
                 // Continue the deep merge
                 for (key, val) in incoming_map {
                     Self::root(key.clone()).set(parent, val.clone());
                 }
             }
-
             _ => {
                 *parent = value;
             }


Test patch:
diff --git a/tests/testsuite/merge.rs b/tests/testsuite/merge.rs
index 19a73bbb..d2b8f935 100644
--- a/tests/testsuite/merge.rs
+++ b/tests/testsuite/merge.rs
@@ -1,3 +1,5 @@
+use snapbox::{assert_data_eq, prelude::*, str};
+
 use config::{Config, File, FileFormat, Map};
 
 #[test]
@@ -80,3 +82,290 @@ fn test_merge_whole_config() {
     assert_eq!(config3.get("x").ok(), Some(10));
     assert_eq!(config3.get("y").ok(), Some(25));
 }
+
+#[test]
+#[cfg(feature = "json")]
+/// Test a few scenarios with empty maps:
+fn test_merge_empty_maps() {
+    use std::collections::BTreeMap;
+
+    #[derive(Debug, Deserialize)]
+    #[allow(dead_code)] // temporary while this test is broken
+    struct Settings {
+        profile: BTreeMap<String, Profile>,
+    }
+
+    #[derive(Debug, Default, Deserialize)]
+    #[allow(dead_code)] // temporary while this test is broken
+    struct Profile {
+        name: Option<String>,
+    }
+
+    // * missing_to_empty: no key -> empty map
+    let cfg = Config::builder()
+        .add_source(File::from_str(r#"{ "profile": {} }"#, FileFormat::Json))
+        .add_source(File::from_str(
+            r#"{ "profile": { "missing_to_empty": {} } }"#,
+            FileFormat::Json,
+        ))
+        .build()
+        .unwrap();
+    let res = cfg.try_deserialize::<Settings>();
+    assert_data_eq!(
+        res.unwrap().to_debug(),
+        str![[r#"
+Settings {
+    profile: {
+        "missing_to_empty": Profile {
+            name: None,
+        },
+    },
+}
+
+"#]]
+    );
+
+    // * missing_to_non_empty: no key -> map with k/v
+    let cfg = Config::builder()
+        .add_source(File::from_str(r#"{ "profile": {} }"#, FileFormat::Json))
+        .add_source(File::from_str(
+            r#"{ "profile": { "missing_to_non_empty": { "name": "bar" } } }"#,
+            FileFormat::Json,
+        ))
+        .build()
+        .unwrap();
+    let res = cfg.try_deserialize::<Settings>();
+    assert_data_eq!(
+        res.unwrap().to_debug(),
+        str![[r#"
+Settings {
+    profile: {
+        "missing_to_non_empty": Profile {
+            name: Some(
+                "bar",
+            ),
+        },
+    },
+}
+
+"#]]
+    );
+
+    // * empty_to_empty: empty map -> empty map
+    let cfg = Config::builder()
+        .add_source(File::from_str(
+            r#"{ "profile": { "empty_to_empty": {} } }"#,
+            FileFormat::Json,
+        ))
+        .add_source(File::from_str(
+            r#"{ "profile": { "empty_to_empty": {} } }"#,
+            FileFormat::Json,
+        ))
+        .build()
+        .unwrap();
+    let res = cfg.try_deserialize::<Settings>();
+    assert_data_eq!(
+        res.unwrap().to_debug(),
+        str![[r#"
+Settings {
+    profile: {
+        "empty_to_empty": Profile {
+            name: None,
+        },
+    },
+}
+
+"#]]
+    );
+
+    // * empty_to_non_empty: empty map -> map with k/v
+    let cfg = Config::builder()
+        .add_source(File::from_str(
+            r#"{ "profile": { "empty_to_non_empty": {} } }"#,
+            FileFormat::Json,
+        ))
+        .add_source(File::from_str(
+            r#"{ "profile": { "empty_to_non_empty": { "name": "bar" } } }"#,
+            FileFormat::Json,
+        ))
+        .build()
+        .unwrap();
+    let res = cfg.try_deserialize::<Settings>();
+    assert_data_eq!(
+        res.unwrap().to_debug(),
+        str![[r#"
+Settings {
+    profile: {
+        "empty_to_non_empty": Profile {
+            name: Some(
+                "bar",
+            ),
+        },
+    },
+}
+
+"#]]
+    );
+
+    // * non_empty_to_empty: map with k/v -> empty map
+    let cfg = Config::builder()
+        .add_source(File::from_str(
+            r#"{ "profile": { "non_empty_to_empty": { "name": "foo" } } }"#,
+            FileFormat::Json,
+        ))
+        .add_source(File::from_str(
+            r#"{ "profile": { "non_empty_to_empty": {} } }"#,
+            FileFormat::Json,
+        ))
+        .build()
+        .unwrap();
+    let res = cfg.try_deserialize::<Settings>();
+    assert_data_eq!(
+        res.unwrap().to_debug(),
+        str![[r#"
+Settings {
+    profile: {
+        "non_empty_to_empty": Profile {
+            name: Some(
+                "foo",
+            ),
+        },
+    },
+}
+
+"#]]
+    );
+
+    // * non_empty_to_non_empty: map with k/v -> map with k/v (override)
+    let cfg = Config::builder()
+        .add_source(File::from_str(
+            r#"{ "profile": { "non_empty_to_non_empty": { "name": "foo" } } }"#,
+            FileFormat::Json,
+        ))
+        .add_source(File::from_str(
+            r#"{ "profile": { "non_empty_to_non_empty": { "name": "bar" } } }"#,
+            FileFormat::Json,
+        ))
+        .build()
+        .unwrap();
+    let res = cfg.try_deserialize::<Settings>();
+    assert_data_eq!(
+        res.unwrap().to_debug(),
+        str![[r#"
+Settings {
+    profile: {
+        "non_empty_to_non_empty": Profile {
+            name: Some(
+                "bar",
+            ),
+        },
+    },
+}
+
+"#]]
+    );
+
+    // * null_to_empty: null -> empty map
+    // * null_to_non_empty: null -> map with k/v
+    // * int_to_empty: int -> empty map
+    // * int_to_non_empty: int -> map with k/v
+    let cfg = Config::builder()
+        .add_source(File::from_str(
+            r#"{ "profile": { "null_to_empty": null } }"#,
+            FileFormat::Json,
+        ))
+        .add_source(File::from_str(
+            r#"{ "profile": { "null_to_empty": {} } }"#,
+            FileFormat::Json,
+        ))
+        .build()
+        .unwrap();
+    let res = cfg.try_deserialize::<Settings>();
+    assert_data_eq!(
+        res.unwrap().to_debug(),
+        str![[r#"
+Settings {
+    profile: {
+        "null_to_empty": Profile {
+            name: None,
+        },
+    },
+}
+
+"#]]
+    );
+
+    // * null_to_non_empty: null -> map with k/v
+    let cfg = Config::builder()
+        .add_source(File::from_str(
+            r#"{ "profile": { "null_to_non_empty": null } }"#,
+            FileFormat::Json,
+        ))
+        .add_source(File::from_str(
+            r#"{ "profile": { "null_to_non_empty": { "name": "bar" } } }"#,
+            FileFormat::Json,
+        ))
+        .build()
+        .unwrap();
+    let res = cfg.try_deserialize::<Settings>();
+    assert_data_eq!(
+        res.unwrap().to_debug(),
+        str![[r#"
+Settings {
+    profile: {
+        "null_to_non_empty": Profile {
+            name: Some(
+                "bar",
+            ),
+        },
+    },
+}
+
+"#]]
+    );
+
+    // * int_to_empty: int -> empty map
+    let cfg = Config::builder()
+        .add_source(File::from_str(
+            r#"{ "profile": { "int_to_empty": 42 } }"#,
+            FileFormat::Json,
+        ))
+        .add_source(File::from_str(
+            r#"{ "profile": { "int_to_empty": {} } }"#,
+            FileFormat::Json,
+        ))
+        .build()
+        .unwrap();
+    let res = cfg.try_deserialize::<Settings>();
+    assert_data_eq!(
+        res.unwrap().to_debug(),
+        str![[r#"
+Settings {
+    profile: {
+        "int_to_empty": Profile {
+            name: None,
+        },
+    },
+}
+
+"#]]
+    );
+
+    // * int_to_non_empty: int -> map with k/v
+    let cfg = Config::builder()
+        .add_source(File::from_str(
+            r#"{ "profile": { "int_to_non_empty": 42 } }"#,
+            FileFormat::Json,
+        ))
+        .add_source(File::from_str(
+            r#"{ "int_to_non_empty": { "name": "bar" } }"#,
+            FileFormat::Json,
+        ))
+        .build()
+        .unwrap();
+    let res = cfg.try_deserialize::<Settings>();
+    assert_data_eq!(
+        res.unwrap_err().to_string(),
+        str!["invalid type: integer `42`, expected struct Profile for key `profile.int_to_non_empty`"]
+    );
+}