使用Rust将Bluesky FeedViewPost序列化为BSON存入MongoDB时,如何解决i128不支持的序列化错误?
我太懂你这个痛点了——用Bluesky的SDK拿到FeedViewPost后,想存MongoDB却卡在i128序列化上,而且这些带i128的结构体还是第三方crate定义的,没法直接改字段属性对吧?别慌,咱们有几个可行的方案,从快速救急到长期稳健的都有,一起来看看。
方案一:快速救急——JSON中转法
这是最简单的临时解决方案,利用serde_json会自动把i128序列化为字符串的特性,先转成JSON Value再转成BSON,绕过原生i128的限制。
首先确保Cargo.toml里有serde_json依赖:
[dependencies] # 其他依赖保持不变 serde_json = "1.0"
然后修改你的main函数里的序列化逻辑:
use mongodb::bson; use serde_json; #[tokio::main] async fn main() { let runner = runner::PublicRunner::new().await; let uri = vec!["at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3lawynrmfzk2f".into()]; let output_data = runner.get_posts(uri).await; // 先转成JSON Value,再转BSON let json_val = serde_json::to_value(&output_data) .expect("Unable to serialize data to JSON value"); let doc = bson::from_value(json_val) .expect("Unable to convert JSON value to BSON document"); println!("{}", doc.to_string()); } // 你的runner模块保持不变...
这个方法优点是代码改动极小,几分钟就能跑通;缺点是可能会有一些类型隐式转换(比如某些整数变浮点数),适合快速验证功能,生产环境建议用更稳健的方案。
方案二:长期稳健——镜像结构体+手动转换
这个方案是定义和原结构体完全镜像的本地结构体,把所有i128字段替换成BSON支持的类型(比如String或Decimal128),然后实现从原类型到本地类型的转换,完全可控。
步骤1:定义镜像结构体
根据atrium_api里FeedViewPost、Object、Ipld的结构,定义对应的本地结构体,替换i128类型:
use atrium_api::types::{FeedViewPost, Object, Ipld}; use mongodb::bson::Decimal128; use serde::{Serialize, Deserialize}; use std::collections::BTreeMap; // 自定义Ipld,把I128换成Decimal128(保留数值精度) #[derive(Debug, Serialize, Deserialize)] enum MyIpld { Null, Bool(bool), Integer(i64), Float(f64), String(String), Bytes(Vec<u8>), Array(Vec<MyIpld>), Object(BTreeMap<String, MyIpld>), I128(Decimal128), // 用Decimal128存储i128,支持数值操作 } // 实现从原Ipld到MyIpld的转换 impl From<&Ipld> for MyIpld { fn from(ipld: &Ipld) -> Self { match ipld { Ipld::Null => MyIpld::Null, Ipld::Bool(b) => MyIpld::Bool(*b), Ipld::Integer(i) => MyIpld::Integer(*i), Ipld::Float(f) => MyIpld::Float(*f), Ipld::String(s) => MyIpld::String(s.clone()), Ipld::Bytes(b) => MyIpld::Bytes(b.clone()), Ipld::Array(arr) => MyIpld::Array(arr.iter().map(MyIpld::from).collect()), Ipld::Object(obj) => MyIpld::Object( obj.iter().map(|(k, v)| (k.clone(), MyIpld::from(v))).collect() ), Ipld::I128(i) => { let decimal = Decimal128::from_i128(*i) .unwrap_or_else(|_| Decimal128::from_str(&i.to_string()).unwrap()); MyIpld::I128(decimal) } // 处理其他可能的变体 _ => MyIpld::String(format!("Unsupported Ipld variant: {:?}", ipld)), } } } // 自定义Object结构体 #[derive(Debug, Serialize, Deserialize)] struct MyObject { r#type: String, extra_data: MyIpld, did: String, uri: String, // 复制原Object的所有其他字段,保持类型一致 } impl From<&Object> for MyObject { fn from(obj: &Object) -> Self { MyObject { r#type: obj.r#type.clone(), extra_data: MyIpld::from(&obj.extra_data), did: obj.did.clone(), uri: obj.uri.clone(), // 其他字段依次复制,比如cid、created_at等 } } } // 自定义FeedViewPost结构体 #[derive(Debug, Serialize, Deserialize)] struct MyFeedViewPost { post: MyObject, author: MyObject, // 假设author也是Object类型,替换成MyObject // 其他字段比如reply、like_count等,都换成对应的镜像类型 } impl From<&FeedViewPost> for MyFeedViewPost { fn from(feed_post: &FeedViewPost) -> Self { MyFeedViewPost { post: MyObject::from(&feed_post.post), author: MyObject::from(&feed_post.author), // 其他字段依次转换 } } }
步骤2:转换后序列化
在main函数里把原FeedViewPost转成自定义的MyFeedViewPost,再序列化:
#[tokio::main] async fn main() { let runner = runner::PublicRunner::new().await; let uri = vec!["at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3lawynrmfzk2f".into()]; let output_data = runner.get_posts(uri).await; // 转成自定义结构体 let my_data: Vec<MyFeedViewPost> = output_data.iter().map(MyFeedViewPost::from).collect(); // 序列化自定义结构体到BSON let doc = bson::to_document(&my_data) .expect("Unable to serialize data to a BSON document"); println!("{}", doc.to_string()); }
这个方案的优点是完全可控,类型转换透明,生产环境用起来放心;缺点是需要维护镜像结构体,如果atrium_api更新了结构体字段,你需要同步更新本地的镜像结构体,避免数据丢失。
方案三:高级玩法——Serde远程派生
如果不想手动维护镜像结构体,可以用Serde的remote特性,直接给第三方类型注入自定义序列化逻辑,不需要修改原crate的代码。
步骤1:添加Serde依赖
确保Cargo.toml里Serde启用了remote特性:
[dependencies] serde = { version = "1.0", features = ["derive", "remote"] }
步骤2:远程派生自定义序列化
use atrium_api::types::{Ipld, Object}; use mongodb::bson::Decimal128; use serde::{Serialize, Deserialize}; // 针对Ipld做远程派生,自定义I128的序列化 #[derive(Serialize, Deserialize)] #[serde(remote = "Ipld")] enum LocalIpld { Null, Bool(bool), Integer(i64), Float(f64), String(String), Bytes(Vec<u8>), Array(Vec<Ipld>), Object(std::collections::BTreeMap<String, Ipld>), #[serde(serialize_with = "serialize_i128")] I128(i128), } // 自定义i128序列化函数,转成Decimal128 fn serialize_i128<S>(value: &i128, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer, { let decimal = Decimal128::from_i128(*value) .map_err(|e| serde::ser::Error::custom(format!("Failed to convert i128 to Decimal128: {}", e)))?; decimal.serialize(serializer) } // 针对Object做远程派生,使用自定义的Ipld序列化 #[derive(Serialize, Deserialize)] #[serde(remote = "Object")] struct LocalObject { r#type: String, #[serde(with = "LocalIpld")] extra_data: Ipld, did: String, uri: String, // 其他字段保持和原Object一致 }
步骤3:序列化时指定用远程派生逻辑
在序列化时,用#[serde(with = "LocalObject")]指定对Object类型使用自定义逻辑,不过这个方法需要在序列化的根类型上添加属性,如果根类型是Vec<FeedViewPost>,可能需要对FeedViewPost也做远程派生,相对复杂一些,适合对Serde特性熟悉的开发者。
总结
- 快速验证用JSON中转法,代码改动最小;
- 生产环境优先用镜像结构体法,可控性强;
- 追求极致优雅用Serde远程派生,适合Serde熟练用户。
内容来源于stack exchange

