Reputation: 265
I am new to Rust and I am trying to deserialize JSON data using serde library. I have following JSON structure:
{
“foo”: “bar”,
“speech”: “something”
}
or
{
“foo”: “bar”,
“speech”: [“something”, “something else”]
}
or
{
“foo”: “bar”,
}
I.e. speech is optional and it can be either string or array of strings.
I can handle deserializing string/array of string using following approach:
#[derive(Debug, Serialize, Deserialize)]
struct foo {
pub foo: String,
#[serde(deserialize_with = "deserialize_message_speech")]
speech: Vec<String>
}
I can also handle deserializing optional string/string array attribute using approach:
#[derive(Debug, Serialize, Deserialize)]
struct foo {
pub foo: String,
#[serde(skip_serializing_if = "Option::is_none")]
speech: Option<Vec<String>>
}
or
struct foo {
pub foo: String,
#[serde(skip_serializing_if = "Option::is_none")]
speech: Option<String>
}
But combining it all together simply does not work. It seems deserialize_with does not work properly with Option type. Can somebody advice most straightforward and trivial way how to implement this (serde can be pretty complex, I have seen some crazy stuff :) )?
Upvotes: 7
Views: 9870
Reputation: 265
with #[serde(untagged)] it works!
use serde_json;
use std::result::Result;
use std::error::Error;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
enum Speech {
Str(String),
StrArray(Vec<String>),
}
#[derive(Debug, Serialize, Deserialize)]
struct Foo {
pub foo: String,
#[serde(skip_serializing_if = "Option::is_none")]
speech: Option<Speech>,
}
fn main() -> Result<(), Box<dyn Error>> {
let json1 = r#"
{
"foo": "bar",
"speech": "something"
}
"#;
let json2 = r#"
{
"foo": "bar",
"speech": ["something", "something else"]
}
"#;
let json3 = r#"
{
"foo": "bar"
}
"#;
let foo1: Foo = serde_json::from_str(json1)?;
let back_to_str_foo1 = serde_json::to_string(&foo1).unwrap();
println!("foo1 {:#?}", foo1);
println!("back_to_str_foo1 {}", back_to_str_foo1);
let foo2: Foo = serde_json::from_str(json2)?;
let back_to_str_foo2 = serde_json::to_string(&foo2).unwrap();
println!("foo1 {:#?}", foo2);
println!("back_to_str_foo2 {}", back_to_str_foo2);
let foo3: Foo = serde_json::from_str(json3)?;
let back_to_str_foo3 = serde_json::to_string(&foo3).unwrap();
println!("foo1 {:#?}", foo3);
println!("back_to_str_foo3 {}", back_to_str_foo3);
Ok(())
}
Upvotes: 6
Reputation: 8390
Try using an Enum type for the speech
field:
#[derive(Serialize, Deserialize)]
#[serde(untagged)]
enum Speech {
Str(String),
StrArray(Vec<String>),
}
#[derive(Debug, Serialize, Deserialize)]
struct foo {
pub foo: String,
speech: Option<Speech>,
}
Enum is the go-to way to represent a variant type in Rust. See https://serde.rs/enum-representations.html for more detail.
Upvotes: 13