将两种不同类型反序列化为同一类型

Deserialize two different types into the same one

我有一个包含资源字段的简单结构。我希望我的资源总是 Urls。 在我尝试反序列化的实际文件中,资源可以是 URL 或 Path.

这是我的结构:

pub struct Myfile {
    pub resources: Vec<Resource>,
}

pub type Resource = Url;

我想使用 serde 来:

  1. 尝试使用 url 包中的实现反序列化每个 Resource
  2. 如果失败,请尝试将它们中的每一个反序列化为一个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 方便。

我认为以合理的努力做到这一点的关键是意识到 Urlserde::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<_>, _>>()?,
        })
    }
}

Playground


编辑您的 PS:

如果您愿意手动实现反序列化器特征和函数,我想您确实可以选择完全摆脱任何包装器结构或中介 TryFrom 类型:添加一个 #[serde(deserialize_with = …)] 到你的 resources,在那里,首先做一个 <Vec<String>>::deserialize(de)?,然后像往常一样把它变成 Vec<Url>

Playground