Nona
Nona

Reputation: 5462

How to use serde to deserialize a json formatted array of structs within a struct?

Suppose I have the following structs "Wheel" and "Car" where a "Car" can contain a list of Cars (a recursive definition).

use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Wheel {
    #[serde(default, rename(deserialize = "Car"))]
    pub car: Car,
    #[serde(default, rename(deserialize = "Wheel Diameter"))]
    pub wheel_diameter: f64,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Car {
    #[serde(default, rename(deserialize = "Dealer Price"))]
    pub dealer_price: f64,
    #[serde(default, rename(deserialize = "Cars"))]
    pub cars: Vec<Car>,
}

impl Default for Wheel {
    fn default() -> Wheel {
        Wheel {
            car: Car {
                ..Default::default()
            },
            wheel_diameter: 5.0,
        }
    }
}
impl Default for Car {
    fn default() -> Car {
        Car {
            dealer_price: 1.0,
            cars: Vec::new(),
        }
    }
}


fn main() {
    let str_input = r#"[
  {
    "Wheel Diameter": 5.2,
    "Car": {
      "Dealer Price": 500,
      "Cars": [
        {
          "Car": {
            "Dealer Price": 1500
          }
        }
      ]
    }
  }
]"#;
    let cars: Vec<Wheel> = serde_json::from_str(str_input).unwrap();
    println!("cars {:#?}", cars);
}

1) When I run the above with str_input (basically an escape free JSON formatted string), I get the following output:

cars [
    Wheel {
        car: Car {
            dealer_price: 500.0,
            cars: [
                Car {
                    dealer_price: 0.0,
                    cars: [],
                },
            ],
        },
        wheel_diameter: 5.2,
    },
]

The top level initializes the defaults correctly, but the "nested cars" do not get initialized properly in the Vec. How to get this working in serde? I tried adding a with = "serde_with::json::nested" in various places but that didn't seem to work. I would get errors complaining about Error("invalid type: sequence, expected valid json object", line: 1, column: 61

Does this mean I have to write a custom deserialization somehow?

2) What's a good way to ensure the recursive deserialization doesn't "blow up" the memory? Fairly new to Rust. In Golang, you can pretty much "magically" deserialize this type of structure by adding some string labeled attributes onto the struct responsible for housing the deserialized json.

Note: I realize having a vector of "cars" in the Car struct may not "make sense" from a domain modeling perspective, but I had to do some field renaming to ensure I didn't share code in the wild that wasn't supposed to be shared. So the structure of the code reflects the problem that needs solving even though the names may not make sense conceptually.

Upvotes: 0

Views: 3105

Answers (2)

Plecra
Plecra

Reputation: 196

First things first: it doesn't look like the JSON matches your data structure.

You're using a Car key in the array that isn't defined anywhere in rust. This is closer to what you have defined in serde -

    let str_input = r#"[
  {
    "Wheel Diameter": 5.2,
    "Car": {
      "Dealer Price": 500,
      "Cars": [
        {
          "Dealer Price": 1500
        }
      ]
    }
  }
]"#;

Also, the default attribute doesn't use the struct's Default implementation - it uses the field's. In the case of f64, that's 0. Instead, you can write default = "15.0"

Upvotes: 1

Stargateur
Stargateur

Reputation: 26697

Your representation don't make much sense, the json you give show that you mix up the concept of Cars with Car, in wheel, cars is a object Car, in Car, cars is a Vec something that have a Car. That doesn't make sense. The json look wrong. Anyway, to solve this problem in Rust you need to express the difference, like:

You could use Wheel because it already have a car field:

#[serde(default, rename =  "Cars")]
pub cars: Vec<Wheel>,

use a intermediate structure:

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Foo {
    #[serde(default, rename =  "Car")]
    car: Car,
}

and so change Car.cars to:

#[serde(default, rename =  "Cars")]
pub cars: Vec<Foo>,

or you could use an enum cause it look like your Cars is an enum where you only show to us one type:

#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum Foo {
    Car(Car),
}

You could also reverse the problem with:

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Wheel {
    #[serde(flatten, default)]
    pub car: Foo, // or pub foo: Foo, if it make more sense
    #[serde(default, rename = "Wheel Diameter")]
    pub wheel_diameter: f64,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Car {
    #[serde(default, rename = "Dealer Price")]
    pub dealer_price: f64,
    #[serde(default, rename =  "Cars")]
    pub cars: Vec<Foo>, // or foos if it make more sense
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum Foo {
    Car(Car),
}

You could also do a custom de deserialization.

For your memory concern Rust will stop you if you try to make a recursive type, Vec already offer you the indirection you need to prevent that.

Upvotes: 1

Related Questions