将两种不同类型反序列化为同一类型
Deserialize two different types into the same one
我有一个包含资源字段的简单结构。我希望我的资源总是 Urls。
在我尝试反序列化的实际文件中,资源可以是 URL 或 Path.
这是我的结构:
pub struct Myfile {
pub resources: Vec<Resource>,
}
pub type Resource = Url;
我想使用 serde 来:
- 尝试使用 url 包中的实现反序列化每个
Resource
。
- 如果失败,请尝试将它们中的每一个反序列化为一个Path,然后使用
url::from_*_path()
得到一个Url。
我正在尝试改编 string or map,map and structure 示例,但我很难理解从哪里开始。
由于我的最终结果将是 Url,这些示例似乎表明我应该为 Url 实施反序列化。但我仍然需要当前的实施。我的 Resource
是一个别名,所以我无法为它实现反序列化。
是否有任何简单的方法可以将 Paths 和 Urls 反序列化为 Urls?
PS:我可能会选择建议的答案,但在通读 this post 之后,我尝试了以下似乎也有效的方法:
#[derive(Clone, Debug)]
pub struct Resource(Url);
impl<'de> Deserialize<'de> for Resource {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where D: Deserializer<'de>
{
let s = String::deserialize(deserializer)?;
if let path = Path::new(&s) {
if path.is_file() {
Ok(Resource(Url::from_file_path(path).unwrap()))
} else {
Ok(Resource(Url::from_directory_path(path).unwrap()))
}
} else {
Url::deserialize(s.into_deserializer()).map(|x| Resource(x))
}
}
}
但使用起来不如常规 Urls 方便。
我认为以合理的努力做到这一点的关键是意识到 Url
的 serde::Deserialize
也只是 cooking with water,即只需要一个字符串并调用 Url::parse
就可以了。
所以是时候部署我最喜欢的 serde 技巧了:反序列化为 serde 可以轻松处理的结构:
#[derive(Deserialize)]
pub struct MyfileDeserialize {
pub resources: Vec<String>,
}
告诉 serde 它应该从那个容易处理的结构中得到你最终想要的结构:
#[derive(Deserialize, Debug)]
#[serde(try_from = "MyfileDeserialize")]
pub struct Myfile {
pub resources: Vec<Resource>,
}
最后,你只需要定义如何将MyfileDeserialize
变成Myfile
即可。
impl TryFrom<MyfileDeserialize> for Myfile {
type Error = &'static str; // TODO: replace with anyhow or a proper error type
fn try_from(t: MyfileDeserialize) -> Result<Self, &'static str> {
Ok(Self {
resources: t
.resources
.into_iter()
.map(|url| {
if let Ok(url) = Url::parse(&url) {
return Ok(url);
};
if let Ok(url) = Url::from_file_path(url) {
return Ok(url);
};
// try more from_*_path here.
Err("Can't as url or path")
})
.collect::<Result<Vec<_>, _>>()?,
})
}
}
编辑您的 PS:
如果您愿意手动实现反序列化器特征和函数,我想您确实可以选择完全摆脱任何包装器结构或中介 TryFrom
类型:添加一个 #[serde(deserialize_with = …)]
到你的 resources
,在那里,首先做一个 <Vec<String>>::deserialize(de)?
,然后像往常一样把它变成 Vec<Url>
。
我有一个包含资源字段的简单结构。我希望我的资源总是 Urls。 在我尝试反序列化的实际文件中,资源可以是 URL 或 Path.
这是我的结构:
pub struct Myfile {
pub resources: Vec<Resource>,
}
pub type Resource = Url;
我想使用 serde 来:
- 尝试使用 url 包中的实现反序列化每个
Resource
。 - 如果失败,请尝试将它们中的每一个反序列化为一个Path,然后使用
url::from_*_path()
得到一个Url。
我正在尝试改编 string or map,map and structure 示例,但我很难理解从哪里开始。
由于我的最终结果将是 Url,这些示例似乎表明我应该为 Url 实施反序列化。但我仍然需要当前的实施。我的 Resource
是一个别名,所以我无法为它实现反序列化。
是否有任何简单的方法可以将 Paths 和 Urls 反序列化为 Urls?
PS:我可能会选择建议的答案,但在通读 this post 之后,我尝试了以下似乎也有效的方法:
#[derive(Clone, Debug)]
pub struct Resource(Url);
impl<'de> Deserialize<'de> for Resource {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where D: Deserializer<'de>
{
let s = String::deserialize(deserializer)?;
if let path = Path::new(&s) {
if path.is_file() {
Ok(Resource(Url::from_file_path(path).unwrap()))
} else {
Ok(Resource(Url::from_directory_path(path).unwrap()))
}
} else {
Url::deserialize(s.into_deserializer()).map(|x| Resource(x))
}
}
}
但使用起来不如常规 Urls 方便。
我认为以合理的努力做到这一点的关键是意识到 Url
的 serde::Deserialize
也只是 cooking with water,即只需要一个字符串并调用 Url::parse
就可以了。
所以是时候部署我最喜欢的 serde 技巧了:反序列化为 serde 可以轻松处理的结构:
#[derive(Deserialize)]
pub struct MyfileDeserialize {
pub resources: Vec<String>,
}
告诉 serde 它应该从那个容易处理的结构中得到你最终想要的结构:
#[derive(Deserialize, Debug)]
#[serde(try_from = "MyfileDeserialize")]
pub struct Myfile {
pub resources: Vec<Resource>,
}
最后,你只需要定义如何将MyfileDeserialize
变成Myfile
即可。
impl TryFrom<MyfileDeserialize> for Myfile {
type Error = &'static str; // TODO: replace with anyhow or a proper error type
fn try_from(t: MyfileDeserialize) -> Result<Self, &'static str> {
Ok(Self {
resources: t
.resources
.into_iter()
.map(|url| {
if let Ok(url) = Url::parse(&url) {
return Ok(url);
};
if let Ok(url) = Url::from_file_path(url) {
return Ok(url);
};
// try more from_*_path here.
Err("Can't as url or path")
})
.collect::<Result<Vec<_>, _>>()?,
})
}
}
编辑您的 PS:
如果您愿意手动实现反序列化器特征和函数,我想您确实可以选择完全摆脱任何包装器结构或中介 TryFrom
类型:添加一个 #[serde(deserialize_with = …)]
到你的 resources
,在那里,首先做一个 <Vec<String>>::deserialize(de)?
,然后像往常一样把它变成 Vec<Url>
。