如何在Rust中使用Serde对容器进行“反序列化”

时间:2019-02-19 08:30:13

标签: rust deserialization serde toml

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
}

1 个答案:

答案 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

这项工作符合预期。