MVCE:
use std::collections::HashMap;
use std::fmt;
use std::marker::PhantomData;
use std::str::FromStr;
use serde; // 1.0.85
use serde::de::{self, MapAccess, Visitor}; // 1.0.85
use serde_derive::Deserialize; // 1.0.85
use toml; // 0.4.10
use void::Void; // 1.0.2
// See: https://serde.rs/string-or-struct.html
fn string_or_struct<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where
T: serde::Deserialize<'de> + FromStr<Err = Void>,
D: serde::Deserializer<'de>,
{
// This is a Visitor that forwards string types to T's `FromStr` impl and
// forwards map types to T's `Deserialize` impl. The `PhantomData` is to
// keep the compiler from complaining about T being an unused generic type
// parameter. We need T in order to know the Value type for the Visitor
// impl.
struct StringOrStruct<T>(PhantomData<fn() -> T>);
impl<'de, T> Visitor<'de> for StringOrStruct<T>
where
T: serde::Deserialize<'de> + FromStr<Err = Void>,
{
type Value = T;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("string or map")
}
fn visit_str<E>(self, value: &str) -> Result<T, E>
where
E: de::Error,
{
Ok(FromStr::from_str(value).unwrap())
}
fn visit_map<M>(self, visitor: M) -> Result<T, M::Error>
where
M: MapAccess<'de>,
{
// `MapAccessDeserializer` is a wrapper that turns a `MapAccess`
// into a `Deserializer`, allowing it to be used as the input to T's
// `Deserialize` implementation. T then deserializes itself using
// the entries from the map visitor.
serde::Deserialize::deserialize(de::value::MapAccessDeserializer::new(visitor))
}
}
deserializer.deserialize_any(StringOrStruct(PhantomData))
}
impl FromStr for Obj {
type Err = Void;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Obj {
x: 0,
y: s.to_owned(),
})
}
}
// ----------------------------------------------------------------------------
#[derive(Debug, Deserialize)]
struct Obj {
x: isize,
y: String,
}
#[derive(Debug, Deserialize)]
struct Simple {
#[serde(deserialize_with = "string_or_struct")]
obj: Obj,
}
#[derive(Debug, Deserialize)]
struct InsideHashMap {
objs: HashMap<String, Obj>,
}
fn main() {
// Basic deserialization of Obj
let toml = r#"
x = 5
y = "hello"
"#;
let obj: Obj = toml::from_str(toml).unwrap();
println!("{:?}", obj);
// Basic deserialization of Obj as a field in a struct
let toml = r#"
[obj]
x = 5
y = "hello"
"#;
let simple: Simple = toml::from_str(toml).unwrap();
println!("{:?}", simple);
// Basic deserialization of Obj as a field in a struct as a string or struct
let toml = r#"
obj = "hello"
"#;
let simple: Simple = toml::from_str(toml).unwrap();
println!("{:?}", simple);
// Deserialization of an Obj inside a HashMap
let toml = r#"
[objs]
a = { x = 5, y = "hello" }
"#;
let working: InsideHashMap = toml::from_str(toml).unwrap();
println!("{:?}", working);
// Deserialization of Obj inside a HashMap field as a string or struct
let toml = r#"
[objs]
a = "hello"
"#;
let not_working: InsideHashMap = toml::from_str(toml).unwrap();
println!("{:?}", not_working);
}
我想使用serde反序列化可以将结构指定为字符串或常规结构规范的TOML格式
a = "a string"
b = { x = 5, y = "another string" }
在此示例中,我将得到一个看起来像HashMap的
{
"a": Obj { x: 0, y: "a string" },
"b": Obj { x: 5, y: "another string" }
}
我已阅读https://serde.rs/string-or-struct.html,了解如何在结构字段上使用“ deserialize_with”属性。但是,当该结构位于HashMap之类的容器中时,该如何处理?
#[derive(Debug, Deserialize)]
struct Obj {
x: isize,
y: String
}
#[derive(Debug, Deserialize)]
struct Simple {
#[serde(deserialize_with = "string_or_struct")]
obj: Obj
}
#[derive(Debug, Deserialize)]
struct InsideHashMap {
objs: HashMap<String, Obj> // <-- how can I use "deserialize_with" on Obj here
}
答案 0 :(得分:1)
首先,我们需要另一个结构为HashMap使用import '@webcomponents/shadydom';
:
deserialize_with
所以我们可以这样写:
#[derive(Debug, Deserialize)]
struct Flatten {
#[serde(deserialize_with = "string_or_struct", flatten)]
obj: Obj,
}
这应该起作用,但这不是因为(我真的不知道为什么,看起来像是扁平化的,#[derive(Debug, Deserialize)]
struct InsideHashMap {
objs: HashMap<String, Flatten>,
}
不能一起工作,似乎它没有使用deserialize_with
的实现)
因此,我们必须使用困难的方法,让它实现:
deserialize_with
这项工作符合预期。