使用Serde展平Rust结构体遇挫:#[serde(flatten)]未按预期工作
解决Serde flatten用法误解及结构体展平问题
问题原因
你遇到的panic不是Serde的bug,是对#[serde(flatten)]的用法理解有误。这个属性的作用是将结构体的字段直接平级展开到父对象中,而非读取JSON里嵌套在某个键下的内容。
当你给Library里的artifact字段加flatten时,Serde会期望JSON里的每个library元素是直接包含path和url的对象:
{ "path": "somepath", "url": "someurl" }
但你的实际JSON里,path和url嵌套在artifact键下,Serde找不到直接的path字段,因此触发panic。
解决方案
根据你的需求——保留现有JSON结构,同时让Library结构体直接拥有path和url字段(无嵌套),以下是几种可行实现方式:
方式一:合并结构体+自定义反序列化
直接把Artifact的字段合并到Library,通过自定义反序列化逻辑从JSON的artifact键读取数据:
use serde::{Deserialize, Deserializer}; use serde_json::Result; #[derive(Serialize, Deserialize, Debug)] struct App { version: String, build_date: String, libraries: Vec<Library> } #[derive(Serialize, Debug)] struct Library { path: String, url: String } #[derive(Deserialize)] struct Artifact { path: String, url: String } impl<'de> Deserialize<'de> for Library { fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de>, { let artifact = Artifact::deserialize(deserializer)?; Ok(Library { path: artifact.path, url: artifact.url, }) } }
方式二:使用字段重命名快速实现
如果不想编写自定义反序列化逻辑,可以直接用#[serde(rename)]指定JSON字段路径:
use serde::{Serialize, Deserialize}; #[derive(Serialize, Deserialize, Debug)] struct App { version: String, build_date: String, libraries: Vec<Library> } #[derive(Serialize, Deserialize, Debug)] struct Library { #[serde(rename = "artifact.path")] path: String, #[serde(rename = "artifact.url")] url: String }
方式三:修改JSON结构适配原有代码
如果你能调整JSON格式,去掉libraries里的artifact嵌套,那么你原来的flatten用法就会生效:
修改后的JSON:
{ "version": "0.2a", "build_date": "2023/12/12", "libraries": [ { "path": "somepath", "url": "someurl" }, { "path": "somepath", "url": "someurl" } ] }
对应的结构体(和你最初的代码一致):
#[derive(Serialize, Deserialize, Debug)] struct Library { #[serde(flatten)] artifact: Artifact }
此时Library会通过flatten直接拥有path和url字段。
内容的提问来源于stack exchange,提问作者Samantha Evans
相关产品推荐
相关产品推荐

