【问题标题】:How can I deserialize an enum with an optional internal tag?如何反序列化带有可选内部标签的枚举?
【发布时间】:2020-07-27 17:08:15
【问题描述】:

我使用 Serde 反序列化用 YAML 编写的自定义配置文件。该文件可以包含我表示为内部标记枚举的各种定义:

OfKindFoo:
  kind: Foo
  bar: bar;
  baz: baz;

OfKindQux:
  kind: Qux
  quux: qux;

我在 Rust 中这样表示它:

#[derive(Deserialize)]
#[serde(tag = "kind")]
enum Definition {
    Foo(Foo),
    Qux(Qux),
}

#[derive(Deserialize)]
struct Foo {
    bar: String,
    baz: String,
}

#[derive(Deserialize)]
struct Qux {
    quux: String,
}

我希望用户能够完全省略 kind 字段,当省略时,Serde 应该默认将其反序列化为 Foo

我开始在Definition 上实现Deserialize。我正在尝试将其反序列化为映射并查找 kind 键并根据此键及其是否存在返回相应的枚举变体。

我需要以某种方式将其他映射字段的反序列化分别“转发”到Foo::deserializeBar::deserializefn deserialize 只接受一个参数,即Deserializer。有没有办法将地图“转换”为反序列化器或以其他方式获得在该特定地图上“启动”的反序列化器?

我不能使用#[serde(other)],因为它会为缺少的标签返回Err。即使没有,文档指出other 只能应用于“单元变体”,即不包含任何数据的变体。

【问题讨论】:

    标签: rust serde


    【解决方案1】:

    您可以将主枚举标记为untagged,并将标签添加到具有标签的子结构(此功能is not documented,但被故意添加,因此似乎可能会保留)。不过,没有标签的变体应该在其他变体之后声明,因为 serde 将尝试使用#[serde(untagged)] 以声明的顺序反序列化变体。另请注意,如果在您的实际代码中,变体和结构具有不同的名称,或者您使用的是#[serde(rename)],那么结构的名称对(反)序列化很重要,而不是变体名称。所有这些都适用于您的示例:

    #[derive(Deserialize)]
    #[serde(untagged)]
    enum Definition {
        Qux(Qux),
        Foo(Foo), // variant that doesn't have a tag is the last one
    }
    
    #[derive(Deserialize)]
    struct Foo {
        bar: String,
        baz: String,
    }
    
    #[derive(Deserialize)]
    #[serde(tag = "kind")]
    // if you want the tag to be "qux" instead of "Qux", do
    // #[serde(rename = "qux")]
    // here (or add `, rename = "qux"` to the previous serde attribute)
    struct Qux {
        quux: String,
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-04-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-05-31
      • 2015-07-26
      相关资源
      最近更新 更多