You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Serde实现结构体两个互斥字段仅允许其一存在的反序列化

实现方案

有两种常用的实现方式,可根据你的业务需求选择:

方案一:不修改现有结构体结构,基于try_from做校验

如果你需要保留MyStruct现有field_1、field_2两个可选字段的结构,可以用serde的try_from属性,在反序列化完成后强制校验两个字段的互斥逻辑:

use serde::{Serialize, Deserialize};
use std::convert::TryFrom;

// 中间结构体,用来做原始反序列化,字段和目标结构体完全一致
#[derive(Deserialize)]
struct RawMyStruct {
    field_1: Option<usize>,
    field_2: Option<usize>,
    other_field: String,
}

#[derive(Deserialize, Debug)]
#[serde(try_from = "RawMyStruct")]
struct MyStruct {
    field_1: Option<usize>,
    field_2: Option<usize>,
    other_field: String,
}

impl TryFrom<RawMyStruct> for MyStruct {
    // 示例用简单字符串作为错误类型,生产环境可自定义符合std::error::Error的错误类型获得更详细的报错信息
    type Error = &'static str;

    fn try_from(value: RawMyStruct) -> Result<Self, Self::Error> {
        match (value.field_1.is_some(), value.field_2.is_some()) {
            (true, true) => Err("不能同时存在field_1和field_2字段"),
            (false, false) => Err("必须存在field_1、field_2中的一个字段"),
            _ => Ok(Self {
                field_1: value.field_1,
                field_2: value.field_2,
                other_field: value.other_field,
            })
        }
    }
}

该方案优势是对上层使用方完全透明,结构体对外暴露的字段和原始定义完全一致,不需要修改后续的业务使用逻辑。

方案二:基于枚举优化类型设计,从类型层面保证互斥

如果你可以调整结构体的结构,更推荐用Rust的枚举类型把两个互斥字段抽象成统一的类型,天然在反序列化阶段就保证互斥,不需要额外编写校验逻辑:

use serde::{Serialize, Deserialize};

#[derive(Deserialize, Debug)]
#[serde(rename_all = "snake_case")]
enum MyField {
    Field1(usize),
    Field2(usize),
}

#[derive(Deserialize, Debug)]
struct MyStruct {
    // 将枚举的字段展开到结构体上层,直接匹配输入的field_1、field_2字段
    #[serde(flatten)]
    my_field: MyField,
    other_field: String,
}

// 可根据业务需要添加便捷访问方法,兼容原来的字段访问逻辑
impl MyStruct {
    pub fn get_field_value(&self) -> usize {
        match &self.my_field {
            MyField::Field1(v) => *v,
            MyField::Field2(v) => *v,
        }
    }

    pub fn is_field1(&self) -> bool {
        matches!(&self.my_field, MyField::Field1(_))
    }

    pub fn is_field2(&self) -> bool {
        matches!(&self.my_field, MyField::Field2(_))
    }
}

该方案的原理是通过#[serde(flatten)]把枚举的字段展开到MyStruct的上层,枚举变体经过snake_case重命名后刚好对应field_1、field_2两个字段,serde会自动匹配输入中存在的字段,两个都存在或者都不存在时都会直接返回反序列化错误,符合类型安全设计,后续使用时也不用担心忘记校验两个字段的存在情况。

内容的提问来源于stack exchange,提问作者cactus

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 21:36:03