6

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
}
4

1 回答 1

1

首先,我们需要另一个结构deserialize_with用于我们的 HashMap:

#[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实现)

所以,我们必须使用hard方式,让我们实现它:

use std::collections::HashMap;
use std::fmt;
use std::str::FromStr;

use serde; // 1.0.85
use serde::de::{self, Deserialize, MapAccess, Visitor}; // 1.0.85
use serde::Deserializer;
use serde_derive::Deserialize; // 1.0.85
use toml; // 0.4.10
use void::Void; // 1.0.2

#[derive(Debug)]
struct Obj {
    x: isize,
    y: String,
}

struct ObjVisitor;

// OjbAux is here to avoid implement the deserialiser of the map by hand we can't use
// Obj cause it will cause infinite recursion
#[derive(Debug, Deserialize)]
struct ObjAux {
    x: isize,
    y: String,
}

impl<'de> Visitor<'de> for ObjVisitor {
    type Value = Obj;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("string or map")
    }

    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        Ok(FromStr::from_str(value).unwrap())
    }

    fn visit_map<M>(self, visitor: M) -> Result<Self::Value, M::Error>
    where
        M: MapAccess<'de>,
    {
        let aux: ObjAux = Deserialize::deserialize(de::value::MapAccessDeserializer::new(visitor))?;
        Ok(Obj { x: aux.x, y: aux.y })
    }
}

impl<'de> Deserialize<'de> for Obj {
    fn deserialize<D>(deserializer: D) -> Result<Obj, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer.deserialize_any(ObjVisitor)
    }
}

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 Simple {
    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);
}

这项工作符合预期。

于 2019-02-19T10:57:20.343 回答