问题描述
我有以下 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,Deserialize)]
struct SourceDetails {
name: String,address: String,}
#[derive(Debug,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: {:?}",Err(err) => eprintln!("json: {:?}",}
}
这是 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 变体的建议。
解决方法
正如其他人在 comments 中所说的,Toml 反序列化器 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
的定义相同并使用板条箱,{{ 3}},修改 HashMap
的序列化方式:
use serde_with::{serde_as,DisplayFromStr};
use std::collections::HashMap;
#[serde_as]
#[derive(Debug,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),}
}
}