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
#[serde(serialize_with = "...")]is the way to go. See also github.com/serde-rs/serde/issues/889#[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.