使用 serde 反序列化带有枚举键的 HashMap
Using serde to deserialize a HashMap with a Enum key
我有以下 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"
}
}
}
用 serde_json
反序列化 JSON 效果很好,但是用 toml
反序列化 Toml 会出错。
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 变体的建议。
正如其他人在 , the Toml deserializer doesn't support enums as keys 中所说的那样。
您可以先使用serde
属性将它们转换为String
:
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
,改为修改 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>,
}
这需要 Source
的 FromStr
实施,而不是 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),
}
}
}
我有以下 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"
}
}
}
用 serde_json
反序列化 JSON 效果很好,但是用 toml
反序列化 Toml 会出错。
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 变体的建议。
正如其他人在
您可以先使用serde
属性将它们转换为String
:
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
,改为修改 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>,
}
这需要 Source
的 FromStr
实施,而不是 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),
}
}
}