使用 serde 反序列化带有 Enum 键的 HashMap

2024-01-10

我有以下 Rust 代码,它模拟了一个配置文件,其中包括HashMap键控有一个enum.

use std::collections::HashMap;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
enum Source {
    #[serde(rename = "foo")]
    Foo,
    #[serde(rename = "bar")]
    Bar
}

#[derive(Debug, Clone, Serialize, Deserialize)]
struct SourceDetails {
    name: String,
    address: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
struct Config {
    name: String,
    main_source: Source,
    sources: HashMap<Source, SourceDetails>,
}

fn main() {
    let config_str = std::fs::read_to_string("testdata.toml").unwrap();
    match toml::from_str::<Config>(&config_str) {
        Ok(config) => println!("toml: {:?}", config),
        Err(err) => eprintln!("toml: {:?}", err),
    }

    let config_str = std::fs::read_to_string("testdata.json").unwrap();
    match serde_json::from_str::<Config>(&config_str) {
        Ok(config) => println!("json: {:?}", config),
        Err(err) => eprintln!("json: {:?}", err),
    }
}

这是 Toml 表示:

name = "big test"
main_source = "foo"

[sources]
foo = { name = "fooname", address = "fooaddr" }

[sources.bar]
name = "barname"
address = "baraddr"

这是 JSON 表示形式:

{
  "name": "big test",
  "main_source": "foo",
  "sources": {
    "foo": {
      "name": "fooname",
      "address": "fooaddr"
    },
    "bar": {
      "name": "barname",
      "address": "baraddr"
    }
  }
}

反序列化 JSONserde_json工作完美,但反序列化 Tomltoml给出错误。

Error: Error { inner: ErrorInner { kind: Custom, line: Some(5), col: 0, at: Some(77), message: "invalid type: string \"foo\", expected enum Source", key: ["sources"] } }

如果我改变sources HashMap被锁定String代替Source,JSON 和 Toml 都正确反序列化。

我对 serde 和 toml 还很陌生,所以我正在寻找有关如何正确反序列化 toml 变体的建议。


正如其他人在comments https://stackoverflow.com/questions/68579809/rust-advice-on-using-serde-to-deserialize-a-hashmap-with-a-enum-key/68580953#comment121200639_68579809,Toml 解串器不支持枚举作为键 https://github.com/alexcrichton/toml-rs/issues/212.

您可以使用serde将它们转换为的属性String first:

use std::convert::TryFrom;
use std::fmt;

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(try_from = "String")]
enum Source {
    Foo,
    Bar
}

然后实现一个转换String:

struct SourceFromStrError;

impl fmt::Display for SourceFromStrError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write_str("SourceFromStrError")
    }
}

impl TryFrom<String> for Source {
    type Error = SourceFromStrError;
    fn try_from(s: String) -> Result<Self, Self::Error> {
        match s.as_str() {
            "foo" => Ok(Source::Foo),
            "bar" => Ok(Source::Bar),
            _ => Err(SourceFromStrError),
        }
    }
}

如果您只需要这个HashMap有问题时,您还可以遵循 Toml 问题中的建议,即保留Source同样并使用板条箱,serde_with https://crates.io/crates/serde_with,修改如何HashMap改为序列化:

use serde_with::{serde_as, DisplayFromStr};
use std::collections::HashMap;

#[serde_as]
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Config {
    name: String,
    main_source: Source,
    #[serde_as(as = "HashMap<DisplayFromStr, _>")]
    sources: HashMap<Source, SourceDetails>,
}

这需要一个FromStr实施Source, 而不是TryFrom<String>:

impl FromStr for Source {
    type Err = SourceFromStrError;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
       match s {
            "foo" => Ok(Source::Foo),
            "bar" => Ok(Source::Bar),
            _ => Err(SourceFromStrError),
        }
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 serde 反序列化带有 Enum 键的 HashMap 的相关文章

随机推荐