Repository: toml-rs/toml (language: rust)

Issue:
Newtype as the key of a `HashMap`/`BTreeMap` is not supported
A newtype - a named single-tuple type - cannot be used as the key of a `HashMap` or a `BTreeMap`.

[playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=ecabf606f065a4c82d7c4de0cb1c720d)
```rust
use std::collections::HashMap;
use serde::Deserialize;

#[derive(PartialEq, Eq, Hash, Debug, Deserialize)]
struct NewType(String);

fn main() -> anyhow::Result<()> {
    let map = toml::from_str::<HashMap<NewType, u32>>(r#"
    x = 1
    y = 2
    "#)?;
    println!("{map:?}");
    Ok(())
}
```

```
Error: TOML parse error at line 2, column 5
  |
2 |     x = 1
  |     ^
invalid type: string "x", expected tuple struct NewType
```

This is contrary to `serde_json`.

[playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=7c51f0b4d7b60349402c1cbad6a44bf1)
```rust
use std::collections::HashMap;
use serde::Deserialize;

#[derive(PartialEq, Eq, Hash, Debug, Deserialize)]
struct NewType(String);

fn main() -> anyhow::Result<()> {
    let map = serde_json::from_str::<HashMap<NewType, u32>>(r#"
    {"x": 1, "y": 2}
    "#)?;
    println!("{map:?}");
    Ok(())
}
```

```
{NewType("x"): 1, NewType("y"): 2}
```

I ran into this issue in my project, and I think that this should definitely be supported. 


Discussion / hints:
Ran into the same issue today.
Actually - this might be somewhat expected, as `NewType` does not seem to support deserialization from `&str` by default.

The solution is to modify the newtype so that it behaves just like the underlying type:
```rs
#[derive(PartialEq, Eq, Hash, Debug, Deserialize)]
#[serde(transparent)]
struct NewType(String);
```

With the addition of `#[serde(transparent)]`, the code posted in the issue seems to be working just fine.

Fix patch:
diff --git a/crates/toml_edit/src/de/key.rs b/crates/toml_edit/src/de/key.rs
index 3da41df4..a3b28256 100644
--- a/crates/toml_edit/src/de/key.rs
+++ b/crates/toml_edit/src/de/key.rs
@@ -62,9 +62,20 @@ impl<'de> serde::de::Deserializer<'de> for KeyDeserializer {
         self.deserialize_any(visitor)
     }
 
+    fn deserialize_newtype_struct<V>(
+        self,
+        _name: &'static str,
+        visitor: V,
+    ) -> Result<V::Value, Error>
+    where
+        V: serde::de::Visitor<'de>,
+    {
+        visitor.visit_newtype_struct(self)
+    }
+
     serde::forward_to_deserialize_any! {
         bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq
-        bytes byte_buf map option unit newtype_struct
+        bytes byte_buf map option unit
         ignored_any unit_struct tuple_struct tuple identifier
     }
 }


Test patch:
diff --git a/crates/toml/tests/testsuite/serde.rs b/crates/toml/tests/testsuite/serde.rs
index 82ba00b1..d9d7a6db 100644
--- a/crates/toml/tests/testsuite/serde.rs
+++ b/crates/toml/tests/testsuite/serde.rs
@@ -742,6 +742,25 @@ fn newtype_variant() {
     }
 }
 
+#[test]
+fn newtype_key() {
+    #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Serialize, Deserialize)]
+    struct NewType(String);
+
+    type CustomKeyMap = std::collections::BTreeMap<NewType, u32>;
+
+    equivalent! {
+        [
+            (NewType("x".to_owned()), 1),
+            (NewType("y".to_owned()), 2),
+        ].into_iter().collect::<CustomKeyMap>(),
+        map! {
+            x: Value::Integer(1),
+            y: Value::Integer(2)
+        },
+    }
+}
+
 #[derive(Debug, Default, PartialEq, Serialize, Deserialize)]
 struct CanBeEmpty {
     a: Option<String>,