您可以反序列化映射或字符串中的结构吗?
Can you deserialize a struct from a map or a string?
考虑这个 Config
结构,它包含一个 Host
结构的向量:
use serde::Deserialize;
use std::net::IpAddr;
#[derive(Debug, Deserialize)]
struct Config {
name: String,
hosts: Vec<Host>
}
#[derive(Debug, Deserialize)]
struct Host {
addr: IpAddr,
user: String,
}
使用派生的 Deserialize
实现,可以使用 serde_json
和 serde_yaml
成功反序列化以下 JSON 和 YAML 配置文件:
{
"name": "example",
"hosts": [
{ "addr": "1.1.1.1", "user": "alice" },
{ "addr": "2.2.2.2", "user": "bob" }
]
}
---
name: example
hosts:
- addr: 1.1.1.1
user: alice
- addr: 2.2.2.2
user: bob
但是,我还希望能够从字符串中反序列化 Host
结构。但是,重要的是我还可以从地图中反序列化它,理想情况下矢量可以由两种格式组成。例如:
{
"name": "example",
"hosts": [
"alice@1.1.1.1",
{ "addr": "2.2.2.2", "user": "bob" }
]
}
---
name: example
hosts:
- alice@1.1.1.1
- addr: 2.2.2.2
user: bob
在 Host
结构之上使用 #[serde(try_from = "String")]
,我可以轻松支持字符串反序列化...但是它不再反序列化地图格式了。
serde 网站有一个关于反序列化字符串或结构的 page,但它需要 deserialize_with
属性,该属性只能应用于字段(不适用于结构容器)。我不确定这种技术是否有效,因为我的领域是 Vec<Host>
而不仅仅是 Host
.
这可以实现吗?
您可以为此使用未标记的枚举。结合自定义反序列化器:
use std::str::FromStr;
use serde::{Deserialize, Deserializer};
use std::net::IpAddr;
#[derive(Debug, Deserialize)]
struct Config {
name: String,
hosts: Vec<Host>,
}
#[derive(Debug, Deserialize)]
struct InnerHost {
addr: IpAddr,
user: String,
}
#[derive(Debug, Deserialize)]
#[serde(untagged)]
enum Host {
#[serde(deserialize_with = "deserialize_host_from_str")]
FromStr(InnerHost),
FromDict(InnerHost),
}
fn deserialize_host_from_str<'de, D>(deserializer: D) -> Result<InnerHost, D::Error>
where
D: Deserializer<'de>,
{
let value = String::deserialize(deserializer)?;
// parse the value and return host
Ok(InnerHost {
addr: IpAddr::from_str("1.1.1.1").unwrap(),
user: "foobar".to_string(),
})
}
fn main() {
let data = r#"{
"name": "example",
"hosts": [
"alice@1.1.1.1",
{ "addr": "2.2.2.2", "user": "bob" }
]
}"#;
let config : Config = serde_json::from_str(data).unwrap();
println!("{:?}", config);
}
为方便起见,您可以将 Host
的 AsRef
实现添加到 InnerHost
或从枚举中提取它的方法。
这是一个更简洁的解决方案,不需要公开包装器类型。修改自 here.
use serde::{Deserialize, Deserializer};
use std::net::IpAddr;
use std::str::FromStr;
#[derive(Debug, Deserialize)]
struct Config {
name: String,
hosts: Vec<Host>,
}
#[derive(Debug)]
struct Host {
addr: IpAddr,
user: String,
}
impl<'de> Deserialize<'de> for Host {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
#[derive(Deserialize)]
#[serde(remote = "Host")] // cannot use `Self` here
struct This {
addr: IpAddr,
user: String,
}
#[derive(Deserialize)]
#[serde(untagged)]
enum Helper {
Short(String),
#[serde(with = "This")]
Full(Host),
}
Ok(match Helper::deserialize(deserializer)? {
Helper::Short(value) => {
let _ = value; // parse value here
Self {
addr: IpAddr::from_str("1.1.1.1").unwrap(),
user: "foobar".to_string(),
}
}
Helper::Full(this) => this,
})
}
}
fn main() {
let data = r#"{
"name": "example",
"hosts": [
"alice@1.1.1.1",
{ "addr": "2.2.2.2", "user": "bob" }
]
}"#;
let config: Config = serde_json::from_str(data).unwrap();
println!("{:?}", config);
}
所有反序列化逻辑都在 Host
类型本身内完成,对其调用者没有任何约定(我指的是 Config
类型)。
关键思想是使用 remote
属性让默认的反序列化函数在另一个命名空间中生成。
考虑这个 Config
结构,它包含一个 Host
结构的向量:
use serde::Deserialize;
use std::net::IpAddr;
#[derive(Debug, Deserialize)]
struct Config {
name: String,
hosts: Vec<Host>
}
#[derive(Debug, Deserialize)]
struct Host {
addr: IpAddr,
user: String,
}
使用派生的 Deserialize
实现,可以使用 serde_json
和 serde_yaml
成功反序列化以下 JSON 和 YAML 配置文件:
{
"name": "example",
"hosts": [
{ "addr": "1.1.1.1", "user": "alice" },
{ "addr": "2.2.2.2", "user": "bob" }
]
}
---
name: example
hosts:
- addr: 1.1.1.1
user: alice
- addr: 2.2.2.2
user: bob
但是,我还希望能够从字符串中反序列化 Host
结构。但是,重要的是我还可以从地图中反序列化它,理想情况下矢量可以由两种格式组成。例如:
{
"name": "example",
"hosts": [
"alice@1.1.1.1",
{ "addr": "2.2.2.2", "user": "bob" }
]
}
---
name: example
hosts:
- alice@1.1.1.1
- addr: 2.2.2.2
user: bob
在 Host
结构之上使用 #[serde(try_from = "String")]
,我可以轻松支持字符串反序列化...但是它不再反序列化地图格式了。
serde 网站有一个关于反序列化字符串或结构的 page,但它需要 deserialize_with
属性,该属性只能应用于字段(不适用于结构容器)。我不确定这种技术是否有效,因为我的领域是 Vec<Host>
而不仅仅是 Host
.
这可以实现吗?
您可以为此使用未标记的枚举。结合自定义反序列化器:
use std::str::FromStr;
use serde::{Deserialize, Deserializer};
use std::net::IpAddr;
#[derive(Debug, Deserialize)]
struct Config {
name: String,
hosts: Vec<Host>,
}
#[derive(Debug, Deserialize)]
struct InnerHost {
addr: IpAddr,
user: String,
}
#[derive(Debug, Deserialize)]
#[serde(untagged)]
enum Host {
#[serde(deserialize_with = "deserialize_host_from_str")]
FromStr(InnerHost),
FromDict(InnerHost),
}
fn deserialize_host_from_str<'de, D>(deserializer: D) -> Result<InnerHost, D::Error>
where
D: Deserializer<'de>,
{
let value = String::deserialize(deserializer)?;
// parse the value and return host
Ok(InnerHost {
addr: IpAddr::from_str("1.1.1.1").unwrap(),
user: "foobar".to_string(),
})
}
fn main() {
let data = r#"{
"name": "example",
"hosts": [
"alice@1.1.1.1",
{ "addr": "2.2.2.2", "user": "bob" }
]
}"#;
let config : Config = serde_json::from_str(data).unwrap();
println!("{:?}", config);
}
为方便起见,您可以将 Host
的 AsRef
实现添加到 InnerHost
或从枚举中提取它的方法。
这是一个更简洁的解决方案,不需要公开包装器类型。修改自 here.
use serde::{Deserialize, Deserializer};
use std::net::IpAddr;
use std::str::FromStr;
#[derive(Debug, Deserialize)]
struct Config {
name: String,
hosts: Vec<Host>,
}
#[derive(Debug)]
struct Host {
addr: IpAddr,
user: String,
}
impl<'de> Deserialize<'de> for Host {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
#[derive(Deserialize)]
#[serde(remote = "Host")] // cannot use `Self` here
struct This {
addr: IpAddr,
user: String,
}
#[derive(Deserialize)]
#[serde(untagged)]
enum Helper {
Short(String),
#[serde(with = "This")]
Full(Host),
}
Ok(match Helper::deserialize(deserializer)? {
Helper::Short(value) => {
let _ = value; // parse value here
Self {
addr: IpAddr::from_str("1.1.1.1").unwrap(),
user: "foobar".to_string(),
}
}
Helper::Full(this) => this,
})
}
}
fn main() {
let data = r#"{
"name": "example",
"hosts": [
"alice@1.1.1.1",
{ "addr": "2.2.2.2", "user": "bob" }
]
}"#;
let config: Config = serde_json::from_str(data).unwrap();
println!("{:?}", config);
}
所有反序列化逻辑都在 Host
类型本身内完成,对其调用者没有任何约定(我指的是 Config
类型)。
关键思想是使用 remote
属性让默认的反序列化函数在另一个命名空间中生成。