如何制作可以反序列化为多种类型之一的 config.rs 字段?
How do I make a config.rs field that can deserialize into one of several types?
我在后台使用 config as a means to load external data into my program which uses serde 进行反序列化,但我想要特定字段可以是几种类型之一的能力。
因为我对 Rust 很陌生,所以我发现 documentation 没有多大意义。
我怎样才能使 initial_value
可以是以下几种类型之一:
String
i32
bool
[i32,i32]
我知道我可以使用属性 deserialize_with
来定向到自定义解码器,但除此之外我有点不知所措。
这是我的代码:
use std::collections::HashMap;
use std::fs;
use config_rs::{Config, ConfigError, File};
#[derive(Debug, Deserialize)]
enum InitialValues {
String,
i32,
bool,
}
#[derive(Debug, Deserialize)]
struct Component {
component: String,
initial_value: InitialValues,
}
#[derive(Debug, Deserialize)]
struct Template {
feature_packs: Vec<String>,
components: Vec<Component>,
}
type Name = String;
type Type = String;
#[derive(Debug, Deserialize)]
pub struct Templates {
templates: HashMap<Name, Template>,
}
impl Templates {
pub fn new(file: &str) -> Result<Self, ConfigError> {
let mut templates = Config::new();
templates.merge(File::with_name(file)).unwrap();
templates.try_into()
}
}
#[derive(Debug)]
pub struct Manager {
pub templates: HashMap<Type, Templates>,
}
impl Manager {
pub fn new(dir: &str) -> Self {
let mut manager = Self {
templates: HashMap::new(),
};
'paths: for raw_path in fs::read_dir(dir).unwrap() {
let path = raw_path.unwrap().path();
let file_path = path.clone();
let type_name = path.clone();
let templates = match Templates::new(type_name.to_str().unwrap()) {
Ok(templates) => templates,
Err(message) => {
println!("TemplateManager: file({:?}), {}", &file_path, message);
continue 'paths;
}
};
manager.templates.insert(
file_path.file_stem().unwrap().to_str().unwrap().to_owned(),
templates,
);
}
manager
}
}
示例配置文件:
---
templates:
orc:
feature_packs:
- physical
- basic_identifiers_mob
components:
- component: char
initial_value: T
goblin:
feature_packs:
- physical
- basic_identifiers_mob
components:
- component: char
initial_value: t
将 InitialValues
enum
更改为:
#[derive(Debug, Deserialize)]
#[serde(
rename_all = "lowercase",
untagged
)]
pub enum InitialValue {
Char(char),
String(String),
Int(i32),
Float(f32),
Bool(bool),
Range(Range<i32>)
}
意味着以下内容现在有效:
---
example:
feature_packs:
- example
- example
components:
- component: char
initial_value: T
- component: string
initial_value: 'asdasdasd'
- component: int
initial_value: 2
- component: float
initial_value: 3.2
- component: bool
initial_value: true
- component: range
initial_value:
start: 0
end: 9
它比我想要的要冗长一点,但至少它是可行的。
编辑:感谢@dtolnay 的回答,我能够改进这个问题,现在它完全按照我最初的意图读取数据!
上面的代码已经被修改以反映这一点。
另外,通过 #[serde(flatten)]
到 Templates
像这样:
#[derive(Debug, Deserialize)]
pub struct Templates {
#[serde(flatten)]
templates: HashMap<Name,Template>,
}
我能够消除在文件开头添加 templates:
的需要。
我将反序列化您提供的示例配置,如下所示。
const Y: &str = r#"
---
orc:
feature_packs:
- physical
- basic_identifiers_mob
components:
- component: char
initial_value: T
goblin:
feature_packs:
- physical
- basic_identifiers_mob
components:
- component: char
initial_value: t
"#;
#[macro_use]
extern crate serde_derive;
extern crate serde;
extern crate serde_yaml;
use std::collections::HashMap as Map;
type Templates = Map<String, Template>;
#[derive(Deserialize, Debug)]
struct Template {
feature_packs: Vec<String>,
components: Vec<Component>,
}
#[derive(Deserialize, Debug)]
#[serde(
tag = "component",
content = "initial_value",
rename_all = "lowercase",
)]
enum Component {
Char(char),
String(String),
Int(i32),
Float(f32),
Bool(bool),
Range(Range<i32>),
}
#[derive(Deserialize, Debug)]
struct Range<T> {
start: T,
end: T,
}
fn main() {
println!("{:#?}", serde_yaml::from_str::<Templates>(Y).unwrap());
}
我在后台使用 config as a means to load external data into my program which uses serde 进行反序列化,但我想要特定字段可以是几种类型之一的能力。 因为我对 Rust 很陌生,所以我发现 documentation 没有多大意义。
我怎样才能使 initial_value
可以是以下几种类型之一:
String
i32
bool
[i32,i32]
我知道我可以使用属性 deserialize_with
来定向到自定义解码器,但除此之外我有点不知所措。
这是我的代码:
use std::collections::HashMap;
use std::fs;
use config_rs::{Config, ConfigError, File};
#[derive(Debug, Deserialize)]
enum InitialValues {
String,
i32,
bool,
}
#[derive(Debug, Deserialize)]
struct Component {
component: String,
initial_value: InitialValues,
}
#[derive(Debug, Deserialize)]
struct Template {
feature_packs: Vec<String>,
components: Vec<Component>,
}
type Name = String;
type Type = String;
#[derive(Debug, Deserialize)]
pub struct Templates {
templates: HashMap<Name, Template>,
}
impl Templates {
pub fn new(file: &str) -> Result<Self, ConfigError> {
let mut templates = Config::new();
templates.merge(File::with_name(file)).unwrap();
templates.try_into()
}
}
#[derive(Debug)]
pub struct Manager {
pub templates: HashMap<Type, Templates>,
}
impl Manager {
pub fn new(dir: &str) -> Self {
let mut manager = Self {
templates: HashMap::new(),
};
'paths: for raw_path in fs::read_dir(dir).unwrap() {
let path = raw_path.unwrap().path();
let file_path = path.clone();
let type_name = path.clone();
let templates = match Templates::new(type_name.to_str().unwrap()) {
Ok(templates) => templates,
Err(message) => {
println!("TemplateManager: file({:?}), {}", &file_path, message);
continue 'paths;
}
};
manager.templates.insert(
file_path.file_stem().unwrap().to_str().unwrap().to_owned(),
templates,
);
}
manager
}
}
示例配置文件:
---
templates:
orc:
feature_packs:
- physical
- basic_identifiers_mob
components:
- component: char
initial_value: T
goblin:
feature_packs:
- physical
- basic_identifiers_mob
components:
- component: char
initial_value: t
将 InitialValues
enum
更改为:
#[derive(Debug, Deserialize)]
#[serde(
rename_all = "lowercase",
untagged
)]
pub enum InitialValue {
Char(char),
String(String),
Int(i32),
Float(f32),
Bool(bool),
Range(Range<i32>)
}
意味着以下内容现在有效:
---
example:
feature_packs:
- example
- example
components:
- component: char
initial_value: T
- component: string
initial_value: 'asdasdasd'
- component: int
initial_value: 2
- component: float
initial_value: 3.2
- component: bool
initial_value: true
- component: range
initial_value:
start: 0
end: 9
它比我想要的要冗长一点,但至少它是可行的。
编辑:感谢@dtolnay 的回答,我能够改进这个问题,现在它完全按照我最初的意图读取数据!
上面的代码已经被修改以反映这一点。
另外,通过 #[serde(flatten)]
到 Templates
像这样:
#[derive(Debug, Deserialize)]
pub struct Templates {
#[serde(flatten)]
templates: HashMap<Name,Template>,
}
我能够消除在文件开头添加 templates:
的需要。
我将反序列化您提供的示例配置,如下所示。
const Y: &str = r#"
---
orc:
feature_packs:
- physical
- basic_identifiers_mob
components:
- component: char
initial_value: T
goblin:
feature_packs:
- physical
- basic_identifiers_mob
components:
- component: char
initial_value: t
"#;
#[macro_use]
extern crate serde_derive;
extern crate serde;
extern crate serde_yaml;
use std::collections::HashMap as Map;
type Templates = Map<String, Template>;
#[derive(Deserialize, Debug)]
struct Template {
feature_packs: Vec<String>,
components: Vec<Component>,
}
#[derive(Deserialize, Debug)]
#[serde(
tag = "component",
content = "initial_value",
rename_all = "lowercase",
)]
enum Component {
Char(char),
String(String),
Int(i32),
Float(f32),
Bool(bool),
Range(Range<i32>),
}
#[derive(Deserialize, Debug)]
struct Range<T> {
start: T,
end: T,
}
fn main() {
println!("{:#?}", serde_yaml::from_str::<Templates>(Y).unwrap());
}