如何制作可以反序列化为多种类型之一的 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 可以是以下几种类型之一:

我知道我可以使用属性 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());
}