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

如何使用serde实现同一Rust结构体兼容JSON与INI两种(反)序列化格式

方案实现

以下为可直接落地的实现思路和完整代码:

1. 多格式差异化命名规则处理

要在同一编译环境下同时支持JSON驼峰命名、INI下划线命名两种规则,可通过透明包装类型实现两套序列化逻辑,完全不影响原有JSON的(反)序列化行为:

  • 原有结构体保留现有所有serde属性,适配JSON场景
  • 额外定义一个仅用于INI场景的透明包装结构体,单独实现适配INI规则的Serialize/Deserialize trait,侵入性极低

2. 适配serde_ini的特殊限制

2.1 非字符串值兼容

无需设置条件属性,直接写一个通用的反序列化函数自动适配两种格式:JSON场景直接反序列化数字,INI场景先读字符串再解析为数字,对原有JSON逻辑完全无影响:

use serde::de::{Deserializer, Error as DeError};
use std::str::FromStr;

fn de_optional_num<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error>
where
    T: FromStr,
    T::Err: std::fmt::Display,
    D: Deserializer<'de>,
{
    // JSON场景直接反序列化数字
    let num_res: Result<Option<T>, _> = Option::deserialize(deserializer);
    if let Ok(v) = num_res {
        return Ok(v);
    }
    // INI场景先读字符串再解析
    let s: Option<&str> = Option::deserialize(deserializer)?;
    s.map(|s| s.parse().map_err(DeError::custom)).transpose()
}
2.2 flatten属性兼容

serde_ini本身不支持#[serde(flatten)]属性(INI无嵌套结构概念),直接在INI包装结构体的手动实现里手动展开嵌套字段即可。

完整可运行代码

use serde::{Deserialize, Serialize, Deserializer, Serializer};
use serde::de::Error as DeError;
use serde_with::skip_serializing_none;
use std::str::FromStr;

// 通用数字反序列化适配函数
fn de_optional_num<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error>
where
    T: FromStr,
    T::Err: std::fmt::Display,
    D: Deserializer<'de>,
{
    let num_res: Result<Option<T>, _> = Option::deserialize(deserializer);
    if let Ok(v) = num_res {
        return Ok(v);
    }
    let s: Option<&str> = Option::deserialize(deserializer)?;
    s.map(|s| s.parse().map_err(DeError::custom)).transpose()
}

// 原有JSON适配结构体,逻辑完全保留
#[skip_serializing_none]
#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
struct Foo {
    #[serde(flatten)]
    bar: Option<Bar>,
    
    #[serde(deserialize_with = "de_optional_num")]
    baz_quux: Option<u8>,
}

#[skip_serializing_none]
#[derive(Deserialize, Serialize, Debug, Clone)]
struct Bar {
    #[serde(rename = "plughXyzzySomeRandomStuff", deserialize_with = "de_optional_num")]
    plugh_xyzzy: Option<u8>
}

// INI场景专用透明包装
#[derive(Debug, Clone)]
struct FooIni(Foo);

impl Serialize for FooIni {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        use serde::ser::SerializeMap;
        let mut map = serializer.serialize_map(None)?;
        // 手动展开嵌套的Bar字段
        if let Some(bar) = &self.0.bar {
            if let Some(v) = bar.plugh_xyzzy {
                map.serialize_entry("plugh_xyzzy", &v.to_string())?;
            }
        }
        if let Some(v) = &self.0.baz_quux {
            map.serialize_entry("baz_quux", &v.to_string())?;
        }
        map.end()
    }
}

impl<'de> Deserialize<'de> for FooIni {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let kv = std::collections::HashMap::<String, String>::deserialize(deserializer)?;
        let mut plugh_xyzzy = None;
        if let Some(v) = kv.get("plugh_xyzzy") {
            plugh_xyzzy = Some(v.parse().map_err(DeError::custom)?);
        }
        let bar = plugh_xyzzy.map(|v| Bar { plugh_xyzzy: Some(v) });
        let baz_quux = kv.get("baz_quux").map(|v| v.parse().map_err(DeError::custom)).transpose()?;
        Ok(Self(Foo { bar, baz_quux }))
    }
}

// 原有JSON逻辑不变
impl FromStr for Foo {
    type Err = serde_json::Error;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        serde_json::from_str(s)
    }
}

impl std::fmt::Display for Foo {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", serde_json::to_string(self).map_err(|_| std::fmt::Error)?)
    }
}

// INI对外接口
impl Foo {
    pub fn to_ini(&self) -> Result<String, serde_ini::ser::Error> {
        serde_ini::to_string(&FooIni(self.clone()))
    }
    
    pub fn from_ini(s: &str) -> Result<Self, serde_ini::de::Error> {
        let foo_ini = serde_ini::from_str::<FooIni>(s)?;
        Ok(foo_ini.0)
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 13:39:03