Reputation: 575
I'm trying to deserialize following JSON snippets into a Vec
of struct Shape
:
use serde::{Deserialize, Serialize};
use serde_json::{Result, Value};
#[derive(Debug, Serialize, Deserialize)]
struct Shape { // this struct is not working, for display purpose only
shape_type: String,
d0: f64,
d1: f64,
d2: f64, //optional, like the case of "dot"
d3: f64, //optional, like the case of "circle"
}
let json = r#"
{[
["line", 1.0, 1.0, 2.0, 2.0],
["circle", 3.0, 3.0, 1.0],
["dot", 4.0, 4.0]
]}"#;
let data: Vec<Shape> = match serde_json::from_str(json)?;
Obviously, each type of Shape
needs a String
and different number of f64
to describe it. How should I define the struct of Shape
to deserialize the JSON data as above?
Upvotes: 3
Views: 14914
Reputation: 825
Sounds perfectly doable,
See https://serde.rs/enum-representations.html
Your code would use untagged enum representations and would look like:
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
#[allow(non_camel_case_types)]
enum Shape {
line(String, f64, f64, f64, f64),
circle(String, f64, f64, f64),
dot(String, f64, f64),
}
#[derive(Debug, Serialize, Deserialize)]
struct ShapeList {
shapes: Vec<Shape>
}
fn main() {
let json = r#"{"shapes": [
["line", 1.0, 1.0, 2.0, 2.0],
["circle", 3.0, 3.0, 1.0],
["dot", 4.0, 4.0],
["circle2", 8.0, 3.0, 16.0]
]}"#;
let data: ShapeList = serde_json::from_str(json).unwrap();
println!("{data:#?}");
}
Outputs:
ShapeList {
shapes: [
line(
"line",
1.0,
1.0,
2.0,
2.0,
),
circle(
"circle",
3.0,
3.0,
1.0,
),
dot(
"dot",
4.0,
4.0,
),
circle(
"circle2",
8.0,
3.0,
16.0,
),
],
}
I slightly modified your data to highlight the recognition of the type is not based on the actual value of that first column but on the "signature" of the array.
A better way would eventually be to write your own Serialize/Deserialize implementation which should be documented quite well on the serde website.
Upvotes: 3
Reputation: 42282
How should I define the struct of Shape to deserialize the JSON data as above?
You wouldn't, because the serialisation scheme you want doesn't really make sense to rust, and AFAIK serde doesn't support it (not even if you use an enum
of tuple variant, tag="type"
is not supported for them).
If you really can't or don't want to use a simpler structure & serialisation scheme as described in the other answer, the only option I can see is to implement a custom (de)serialisation scheme.
Especially since the arity changes for each "type", otherwise https://crates.io/crates/serde_tuple would work (although you could always see if skip_serializing_if
works with serde_tuple, that would let you suppress the "extra" fields).
Upvotes: 2
Reputation: 15115
Assuming you have control over the JSON format I strongly recommend making the Shape
type into an enum
that can represent multiple shapes and using serde's derive macros to automatically implement Serialize
and Deserialize
for Shape
. Example:
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct Point {
x: f64,
y: f64,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type")]
enum Shape {
Dot { position: Point },
Line { start: Point, end: Point },
Circle { center: Point, radius: f64 },
}
fn main() {
let shapes = vec![
Shape::Dot {
position: Point { x: 3.0, y: 4.0 },
},
Shape::Line {
start: Point { x: -2.0, y: 1.0 },
end: Point { x: 5.0, y: -3.0 },
},
Shape::Circle {
center: Point { x: 0.0, y: 0.0 },
radius: 7.0,
},
];
let serialized = serde_json::to_string(&shapes).unwrap();
println!("serialized = {}", serialized);
let deserialized: Vec<Shape> = serde_json::from_str(&serialized).unwrap();
println!("deserialized = {:?}", deserialized);
}
If you absolutely cannot change the JSON format then serde cannot help you. Serializing a shape as a heterogeneous array of strings and floats is a very bizarre choice. You have to manually parse it yourself (or at least use some parser crate to help you) and then manually implement the Deserializer trait for it to turn it into a Shape
.
Upvotes: 6