Serde Rust解析字符串、结构体或结构体列表

3
我正在尝试使用Serde在Rust中解析以下JSON。
{
    "threads": [
        {
            "md": [
                {
                    "type": "PARAGRAPH",
                    "value": [
                        {
                            "type": "PLAIN_TEXT",
                            "value": "Plain text msg "
                        },
                        {
                            "type": "INLINE_CODE",
                            "value": {
                                "type": "PLAIN_TEXT",
                                "value": "print('hello')"
                            }
                        },
                        {
                            "type": "ITALIC",
                            "value": [
                                {
                                    "type": "PLAIN_TEXT",
                                    "value": "italic text"
                                }
                            ]
                        }
                    ]
                }
            ]
        }
    ]
}

这段代码如下所示:
use std::fmt;
use std::marker::PhantomData;
use std::str::FromStr;
use serde::{de, Deserialize, Deserializer};
use serde::de::{MapAccess, SeqAccess, Visitor};
use void::Void;
use std::collections::BTreeMap as Map;

impl FromStr for SubValue {
    type Err = Void;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(SubValue{
            value: s.to_string(),
            value_type: None
        })
    }
}

#[derive(Deserialize, Debug)]
pub struct SubValue {
    value: String,

    #[serde(rename = "type")]
    value_type: Option<String>,
}

#[derive(Deserialize, Debug)]
pub struct Value {
    #[serde(rename = "type")]
    value_type: String,
    #[serde(deserialize_with = "string_or_struct")]
    value: SubValue,
}

#[derive(Deserialize, Debug)]
pub struct MessageData {
    #[serde(rename = "type")]
    pub msg_type: String,
    pub value: Vec<Value>,
}

#[derive(Deserialize, Debug)]
pub struct Thread {
    pub md: Vec<MessageData>
}

#[derive(Deserialize, Debug)]
pub struct ThreadList {
    pub threads: Vec<Thread>,
}

fn string_or_struct<'de, T, D>(deserializer: D) -> Result<T, D::Error>
    where
        T: Deserialize<'de> + FromStr<Err=Void>,
        D: Deserializer<'de>,
{
    struct StringOrStruct<T>(PhantomData<fn() -> T>);

    impl<'de, T> Visitor<'de> for StringOrStruct<T>
        where
            T: Deserialize<'de> + FromStr<Err=Void>,
    {
        type Value = T;

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

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

        fn visit_seq<M>(self, seq: M) -> Result<T, M::Error>
            where
                M: SeqAccess<'de>,
        {
            Deserialize::deserialize(de::value::SeqAccessDeserializer::new(seq))
        }

        fn visit_map<M>(self, map: M) -> Result<T, M::Error>
            where M: MapAccess<'de>,
        {
            Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))
        }
    }

    deserializer.deserialize_any(StringOrStruct(PhantomData))
}

fn main() {
    let data =
        "{\n\
            \"threads\": [\n\
            {\n\
                \"md\": [\n\
                {\n\
                    \"type\": \"PARAGRAPH\",\n\
                    \"value\": [\n\
                    {\n\
                        \"type\": \"PLAIN_TEXT\",\n\
                        \"value\": \"Plain text msg \"\n\
                    },\n\
                    {\n\
                        \"type\": \"INLINE_CODE\",\n\
                        \"value\": {\n\
                        \"type\": \"PLAIN_TEXT\",\n\
                        \"value\": \"print('hello')\"\n\
                    }\n\
                    },\n\
                    {\n\
                        \"type\": \"ITALIC\",\n\
                        \"value\": [\n\
                        {\n\
                            \"type\": \"PLAIN_TEXT\",\n\
                            \"value\": \"italic text\"\n\
                        }\n\
                        ]\n\
                    }\n\
                    ]\n\
                }\n\
                ]\n\
            }\n\
            ]\n\
        }\n";

   let v: ThreadList = serde_json::from_str(data).expect("Failed to parse");

    for x in v.threads {
        for md in  x.md{
            for val in md.value {
                println!("{}", val.value.value)
            }
        }
    }
}

这里的大问题在于我无法解析下面的列表italic。 如果可能的话,我想要展开列表,并用“斜体文本”替换值结构,但是它会崩溃并显示 thread 'main' panicked at 'Failed to parse: Error("invalid type: map, expected a string", line: 22, column: 0)', src/main.rs:129:51 我正在尝试使用的API是Rocket Chat的获取线程API。 https://developer.rocket.chat/reference/api/rest-api/endpoints/team-collaboration-endpoints/chat-endpoints/getthreadslist

2
像这样的playground是您正在寻找的吗? - Jmb
#[serde(serialize_with = "...")] 是一个不错的选择。另请参见https://github.com/serde-rs/serde/issues/889 - Chayim Friedman
我在示例中使用了#[serde(serialize_with = "...")],但我无法弄清楚如何处理结构体可能包含字符串、结构体或向量的情况。 - Alex
1个回答

3

一种反序列化数据的方法是使用枚举来表示不同的值类型及其相关内容:

use serde::{Serialize, Deserialize};

#[derive (Serialize, Deserialize, Debug)]
#[serde (tag = "type", content = "value")]
enum Value {
    #[serde (rename = "PARAGRAPH")]
    Paragraph (Vec<Value>),
    #[serde (rename = "PLAIN_TEXT")]
    PlainText (String),
    #[serde (rename = "INLINE_CODE")]
    InlineCode (Box<Value>),
    #[serde (rename = "ITALIC")]
    Italic (Vec<Value>),
}

Playground


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