如何使用serde实现同一Rust结构体兼容JSON与INI两种(反)序列化格式
方案实现
以下为可直接落地的实现思路和完整代码:
1. 多格式差异化命名规则处理
要在同一编译环境下同时支持JSON驼峰命名、INI下划线命名两种规则,可通过透明包装类型实现两套序列化逻辑,完全不影响原有JSON的(反)序列化行为:
- 原有结构体保留现有所有serde属性,适配JSON场景
- 额外定义一个仅用于INI场景的透明包装结构体,单独实现适配INI规则的
Serialize/Deserializetrait,侵入性极低
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
相关产品推荐
相关产品推荐

