Search code examples
jsonstructrustdeserializationserde

How to deserialize a JSON array into a struct using serde?


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?


Solution

  • 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);
    }
    

    playground

    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.