Reputation: 125
So, let's say I am expecting a lot of different JSONs of a known format from a network stream. I define structures for them and wrap them with an enum representing all the possibilities:
use serde::Deserialize;
use serde_json;
#[derive(Deserialize, Debug)]
struct FirstPossibleResponse {
first_field: String,
}
#[derive(Deserialize, Debug)]
struct SecondPossibleResponse {
second_field: String,
}
#[derive(Deserialize, Debug)]
enum ResponseFromNetwork {
FirstPossibleResponse(FirstPossibleResponse),
SecondPossibleResponse(SecondPossibleResponse),
}
Then, being a smart folk, I want to provide myself with a short way of parsing these JSONs into my structures, so I am implementing a trait (and here is the problem):
impl From<String> for ResponseFromNetwork {
fn from(r: String) -> Self {
match serde_json::from_slice(&r.as_bytes()) {
Ok(v) => ResponseFromNetwork::FirstPossibleResponse(v),
Err(_) => match serde_json::from_slice(&r.as_bytes()) {
Ok(v) => ResponseFromNetwork::SecondPossibleResponse(v),
Err(_) => unimplemented!("idk"),
},
}
}
}
...To use it later like this:
fn main() {
let data_first = r#"
{
"first_field": "first_value"
}"#;
let data_second = r#"
{
"second_field": "first_value"
}"#;
print!("{:?}", ResponseFromNetwork::from(data_first.to_owned()));
print!("{:?}", ResponseFromNetwork::from(data_second.to_owned()));
}
So, as mentioned earlier, the problem is that - this match
tree is the only way I got parsing work, and you can imagine - the more variations of different JSONs I might possibly get over the network - the deeper and nastier the tree grows.
I want to have it in some way like ths, e.g. parse it once and then operate depending on the value:
use serde_json::Result;
impl From<String> for ResponseFromNetwork {
fn from(r: String) -> Self {
let parsed: Result<ResponseFromNetwork> = serde_json::from_slice(r.as_bytes());
match parsed {
Ok(v) => {
match v {print!("And here we should match on invariants or something: {:?}", v);
v
}
Err(e) => unimplemented!("{:?}", e),
}
}
}
But it doesn't really work:
thread 'main' panicked at 'not implemented: Error("unknown variant `first_field`, expected `FirstPossibleResponse` or `SecondPossibleResponse`", line: 3, column: 25)', src/main.rs:28:23
Upvotes: 2
Views: 355
Reputation: 155186
#[serde(untagged)]
is designed for precisely that use case. Just add it in front of the definition of enum ResponseFromNetwork
and your code will work the way you want it to:
#[derive(Deserialize, Debug)]
#[serde(untagged)]
enum ResponseFromNetwork {
FirstPossibleResponse(FirstPossibleResponse),
SecondPossibleResponse(SecondPossibleResponse),
}
impl From<String> for ResponseFromNetwork {
fn from(r: String) -> Self {
match serde_json::from_slice(r.as_bytes()) {
Ok(v) => v,
Err(e) => unimplemented!("{:?}", e),
}
}
}
Upvotes: 2
Reputation: 1279
If the formats of the response JSON strings can be extended (maybe not if they are predefined and unchangable), adding a tag field, say "kind", in each JSON, and annotating each variant struct with #[serde(tag = "kind")]
and the enum with #[serde(untagged)]
can address the issue.
[playground]
use serde::Deserialize;
use serde_json;
#[derive(Deserialize, Debug)]
#[serde(tag = "kind")]
struct FirstPossibleResponse {
first_field: String,
}
#[derive(Deserialize, Debug)]
#[serde(tag = "kind")]
struct SecondPossibleResponse {
second_field: String,
}
#[derive(Deserialize, Debug)]
#[serde(tag = "kind")]
struct ThirdPossibleResponse {
third_field: String,
}
#[derive(Deserialize, Debug)]
#[serde(untagged)]
enum ResponseFromNetwork {
FirstPossibleResponse(FirstPossibleResponse),
SecondPossibleResponse(SecondPossibleResponse),
ThirdPossibleResponse(ThirdPossibleResponse),
}
impl From<String> for ResponseFromNetwork {
fn from(r: String) -> Self {
match serde_json::from_slice(&r.as_bytes()) {
Ok(v) => v,
Err(_) => unimplemented!("idk"),
}
}
}
fn main() {
let data_first = r#"
{
"kind":"FirstPossibleResponse",
"first_field": "first_value"
}"#;
let data_second = r#"
{
"kind":"SecondPossibleResponse",
"second_field": "second_value"
}"#;
let data_third = r#"
{
"kind":"ThirdPossibleResponse",
"third_field": "third_value"
}"#;
println!("{:?}", ResponseFromNetwork::from(data_first.to_owned()));
println!("{:?}", ResponseFromNetwork::from(data_second.to_owned()));
println!("{:?}", ResponseFromNetwork::from(data_third.to_owned()));
}
Upvotes: 1