使用 serde_json 反序列化远程结构的映射
Deserialize map of remote structs using serde_json
我有一个用例需要将 JSON 反序列化为“远程”(在另一个板条箱中定义)结构的映射。我在这方面遇到了可笑的困难时期,所以我一定遗漏了一些明显的东西。
以下基本上是所需的最终状态:
use hyper::Uri;
use serde_json;
use std::collections::HashMap;
fn main() {
let data = r#"
{
"/a": "http://example.com/86f7e437faa5a7fce15d1ddcb9eaeaea377667b8",
"/b": "http://example.com/e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98",
"/c": "http://example.com/84a516841ba77a5b4648de2cd0dfcb30ea46dbb4"
}"#;
let map: HashMap<String, Uri> = serde_json::from_str(data).unwrap();
println!("{:?}", map);
}
失败是因为:
the trait bound `Uri: serde::de::Deserialize<'_>` is not satisfied required because of the requirements
on the impl of `serde::de::Deserialize<'_>` for `HashMap<std::string::String, Uri>`
虽然 serde 文档 describe a pretty nasty but potentially viable workaround 用于在远程结构上派生 Deserialize
,但它需要在任何引用容器类型上使用 #[serde(with = "LocalStructRedefinition")]
,这在创建 HashMap
.
凭直觉这个必须是一个常见的用例...有没有办法解决这个不涉及:
- 将数据反序列化为
HashMap<String, String>
- 遍历映射,将值解析为 new
HashMap<String, Uri>
混合使用 Into
、deserialize_with
和 flatten
,您可以实现您想要的:
use serde_json;
use std::collections::HashMap;
use hyper::Uri;
use serde::{de::Error, Deserialize, Deserializer};
#[derive(Debug, Deserialize)]
struct MyUri(#[serde(deserialize_with = "from_uri")] Uri);
#[derive(Debug, Deserialize)]
struct MyUriMap {
#[serde(flatten)]
inner: HashMap<String, MyUri>
}
impl Into<HashMap<String, Uri>> for MyUriMap {
fn into(self) -> HashMap<String, Uri> {
self.inner.into_iter().map(|x| (x.0, x.1.0)).collect()
}
}
fn from_uri<'de, D>(deserializer: D) -> Result<Uri, D::Error>
where
D: Deserializer<'de>,
{
let s: &str = Deserialize::deserialize(deserializer)?;
s.parse().map_err(D::Error::custom)
}
fn main() {
let data = r#"
{
"/a": "http://example.com/86f7e437faa5a7fce15d1ddcb9eaeaea377667b8",
"/b": "http://example.com/e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98",
"/c": "http://example.com/84a516841ba77a5b4648de2cd0dfcb30ea46dbb4"
}"#;
let map: MyUriMap = serde_json::from_str(data).unwrap();
// let map: HashMap<String, Uri> = map.into();
// I think to get HashMap<String, Uri> you have to do an iter as seen in the Into implementation
println!("{:?}", map);
}
PS。在我的回答中,要获得 HashMap 你必须像 Into
implementation
中看到的那样做一个 iter
通过使用 serde_with
crate,大部分反序列化逻辑都可以简化为一个属性。
它的优点是避免了除反序列化之外的任何包装器类型。
#[serde_with::serde_as]
#[derive(serde::Deserialize)]
struct Wrapper(
#[serde_as(as = "HashMap<_, serde_with::DisplayFromStr>")]
HashMap<String, hyper::Uri>
);
fn main() {
let data = r#"
{
"/a": "http://example.com/86f7e437faa5a7fce15d1ddcb9eaeaea377667b8",
"/b": "http://example.com/e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98",
"/c": "http://example.com/84a516841ba77a5b4648de2cd0dfcb30ea46dbb4"
}"#;
let map: Wrapper = serde_json::from_str(data).unwrap();
println!("{:?}", map.0);
}
我有一个用例需要将 JSON 反序列化为“远程”(在另一个板条箱中定义)结构的映射。我在这方面遇到了可笑的困难时期,所以我一定遗漏了一些明显的东西。
以下基本上是所需的最终状态:
use hyper::Uri;
use serde_json;
use std::collections::HashMap;
fn main() {
let data = r#"
{
"/a": "http://example.com/86f7e437faa5a7fce15d1ddcb9eaeaea377667b8",
"/b": "http://example.com/e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98",
"/c": "http://example.com/84a516841ba77a5b4648de2cd0dfcb30ea46dbb4"
}"#;
let map: HashMap<String, Uri> = serde_json::from_str(data).unwrap();
println!("{:?}", map);
}
失败是因为:
the trait bound `Uri: serde::de::Deserialize<'_>` is not satisfied required because of the requirements
on the impl of `serde::de::Deserialize<'_>` for `HashMap<std::string::String, Uri>`
虽然 serde 文档 describe a pretty nasty but potentially viable workaround 用于在远程结构上派生 Deserialize
,但它需要在任何引用容器类型上使用 #[serde(with = "LocalStructRedefinition")]
,这在创建 HashMap
.
凭直觉这个必须是一个常见的用例...有没有办法解决这个不涉及:
- 将数据反序列化为
HashMap<String, String>
- 遍历映射,将值解析为 new
HashMap<String, Uri>
混合使用 Into
、deserialize_with
和 flatten
,您可以实现您想要的:
use serde_json;
use std::collections::HashMap;
use hyper::Uri;
use serde::{de::Error, Deserialize, Deserializer};
#[derive(Debug, Deserialize)]
struct MyUri(#[serde(deserialize_with = "from_uri")] Uri);
#[derive(Debug, Deserialize)]
struct MyUriMap {
#[serde(flatten)]
inner: HashMap<String, MyUri>
}
impl Into<HashMap<String, Uri>> for MyUriMap {
fn into(self) -> HashMap<String, Uri> {
self.inner.into_iter().map(|x| (x.0, x.1.0)).collect()
}
}
fn from_uri<'de, D>(deserializer: D) -> Result<Uri, D::Error>
where
D: Deserializer<'de>,
{
let s: &str = Deserialize::deserialize(deserializer)?;
s.parse().map_err(D::Error::custom)
}
fn main() {
let data = r#"
{
"/a": "http://example.com/86f7e437faa5a7fce15d1ddcb9eaeaea377667b8",
"/b": "http://example.com/e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98",
"/c": "http://example.com/84a516841ba77a5b4648de2cd0dfcb30ea46dbb4"
}"#;
let map: MyUriMap = serde_json::from_str(data).unwrap();
// let map: HashMap<String, Uri> = map.into();
// I think to get HashMap<String, Uri> you have to do an iter as seen in the Into implementation
println!("{:?}", map);
}
PS。在我的回答中,要获得 HashMapInto
implementation
通过使用 serde_with
crate,大部分反序列化逻辑都可以简化为一个属性。
它的优点是避免了除反序列化之外的任何包装器类型。
#[serde_with::serde_as]
#[derive(serde::Deserialize)]
struct Wrapper(
#[serde_as(as = "HashMap<_, serde_with::DisplayFromStr>")]
HashMap<String, hyper::Uri>
);
fn main() {
let data = r#"
{
"/a": "http://example.com/86f7e437faa5a7fce15d1ddcb9eaeaea377667b8",
"/b": "http://example.com/e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98",
"/c": "http://example.com/84a516841ba77a5b4648de2cd0dfcb30ea46dbb4"
}"#;
let map: Wrapper = serde_json::from_str(data).unwrap();
println!("{:?}", map.0);
}