【问题标题】:Serde rust parse string or struct or list of structSerde rust 解析字符串或结构或结构列表
【发布时间】:2026-01-23 10:50:02
【问题描述】:

我正在尝试使用 serde 解析以下 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 是火箭聊天获取线程 api https://developer.rocket.chat/reference/api/rest-api/endpoints/team-collaboration-endpoints/chat-endpoints/getthreadslist

【问题讨论】:

  • 你在找类似playground的东西吗?
  • #[serde(serialize_with = "...")] 是要走的路。另见github.com/serde-rs/serde/issues/889
  • #[serde(serialize_with = "...")] 是我在示例中的内容,但我不知道如何处理该结构可以包含字符串、结构或向量。

标签: json struct rust serde


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

    这不是我想要的,但@Jmb 的建议是一个很好的选择,我可以根据自己的需要进行修改。非常感谢

    【讨论】: