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

8

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个回答

6

首先,我们需要另一个结构体来使用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>,
}

这本应该可以运行,但是实际上并没有,可能是因为(我还不太清楚原因),看起来 "flatten" 和 "deserialize_with" 不能同时使用,好像它没有使用 "deserialize_with" 的实现。

所以,我们必须采用更麻烦的方式,手动实现:

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

这个工作正常。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接