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
CodePudding user response:
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>),
}
CodePudding user response:
It's not exactly what I wanted but the suggestion from @Jmb is a great alternative and I can mend it to my needs. Thanks a lot