3

I'm trying to parse the following JSON in rust with serde

{
    "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"
                                }
                            ]
                        }
                    ]
                }
            ]
        }
    ]
}

The code for this is:

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

The big issue with this is that I'm unable to parse the list below italic. If possible I'd like to flatten the list and replace the value struct with the value "italic text" but it crashes with thread 'main' panicked at 'Failed to parse: Error("invalid type: map, expected a string", line: 22, column: 0)', src/main.rs:129:51 The API I'm trying to use is the rocket chat get thread api https://developer.rocket.chat/reference/api/rest-api/endpoints/team-collaboration-endpoints/chat-endpoints/getthreadslist

3
  • 2
    Is something like this playground what you are looking for? Commented Dec 6, 2021 at 10:20
  • #[serde(serialize_with = "...")] is the way to go. See also github.com/serde-rs/serde/issues/889 Commented Dec 6, 2021 at 10:32
  • #[serde(serialize_with = "...")] is what I have in the example but I can't figure out how I can handle that the struct can contain a string, struct or a vector. Commented Dec 6, 2021 at 10:47

1 Answer 1

3

One way to deserialize your data is to use an enum to represent the different value types and their associated contents:

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

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.