至少一个可选字段

At least one of the optional fields

我有一些结构,其中所有字段都是可选的,但至少必须指定其中一个或至少它们的特定组合。例如:

#[derive(Deserialize)]
struct test {
    #[serde(default)]
    a: String,
    #[serde(default)]
    b: String,
    #[serde(default)]
    c: String,
}

所有字段都是可选的,但至少应提供 bc 之一。知道字段数可能更大,推荐的表示这种逻辑的方法是什么?

作为一个 medium-performance,中等强度的解决方案(与定制的解串器相比),我会

  1. 将反序列化重定向到 HashMap<String, String>
#[derive(Deserialize, Debug)]
#[serde(try_from = "TestDes")]
struct Test {
    a: String,
    b: String,
    c: String,
}

#[derive(Deserialize)]
#[serde(transparent)]
struct TestDes(HashMap<String, String>);
  1. 告诉serde如何将TestDes转换成Test
impl TryFrom<TestDes> for Test {
    type Error = &'static str;
    fn try_from(mut value: TestDes) -> Result<Self, Self::Error> {
        if /* check whether the combination of keys you need is present */ {
            return Err("need at least one value in Test");
        }
        Ok(/*
            extract value.0 into Test here
            requires to repeat 
                x: value.0.delete("x").unwrap_or("".into()),
            for each field
        */)
    }
}

Playground

  • 如果你想要更好的性能,你可以有一个包含 Test 的所有字段的 struct TestDes 作为 Option<String>
  • 如果你不想重复 Test 的字段名称,你可以生成 TestTryInto 实现(和 TestDes,如果你切换Option<String> 变体)在单个宏调用中。

您写道:

All fields are optional but at least one of b or c should be provided.

阅读标题时,我的第一个想法是您希望 所有 个可选字段中至少有一个为空。但由于它只是 bc,这就是我的想法...

有时尝试获取库(serde 在这种情况下)为您做一些事情可能会使事情变得复杂得多,而不是值得的。我很可能只是直接检查反序列化实例的代码。这是 anyhow's macro expect:

的示例
let t: Test = ...; // Get a deserialized instance
ensure!(!t.b.is_empty() || !t.c.is_empty(), "Either b or c needs to be provided!");

我并不是说这应该始终是方法,但在许多情况下,我认为这是最明智(和简单)的做法。通常当我反序列化来自 JSON(或其他一些数据格式)的数据并且我需要验证该数据时,无论如何我都必须实施一些 application-specific 验证 - 不能总是以声明方式表达的东西。通常,验证会去一个地方(比如说一个函数)。我通常依靠库来 运行 至少为我做一些验证(比如必填字段、字段类型等),但有时,即使库可以配置为进行更高级的验证,它根本不值得,或者最终结果不会那么好(例如,我可能想提供一个 more-meaningful 错误消息而不是默认消息/以防库没有为我提供这样的选项/).