Serde populating struct fields post Deserialize

I have a struct which is generally contructed from simple data and then runs some complex/expensive algorithm to populate more persistent data. For example:

#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
#[serde(from = "RectangleShadow")]
pub struct Rectangle {
    length: f64,
    #[serde(skip)]
    area: f64,
    #[serde(skip)]
    diagonal: f64,
}

impl Rectangle {
    fn new (length: f64) -> Self {
        Self {
            length, 
            area: length * length, 
            diagonal: 1.41421 * length
        }
    }
}

Ideally I am trying to permit serialization to and from JSON of this object.
In this case, I want the reconstitution of area and diagonal after it has deserialized length, so that the JSON string will only contain length.

I have managed to achieve this by using a shadow data model and skipping those fields (as above) and adding the serde:from container attribute.

#[derive(Deserialize)]
pub struct RectangleShadow {
    length: f64,
}

impl std::convert::From<RectangleShadow> for Rectangle {
    fn from(shadow: RectangleShadow) -> Self {
        Self::new(shadow.length)
    }
}

Is this the correct way of attaining my goal here. It feels a little overly engineered?

1 Like

Seems like a good solution to me.

Having a different representation of data for serialisation and for the program to work with is very common, and to keep them separate is typically a good idea, as they are separate concerns. You could squish everything together into one struct but it will get messy quickly.

The only thing I'd change is the name, the suffix shadow doesn't make it obvious what its purpose is at first glance, at least not to me.

3 Likes

I would make RectangleShadow private, because it doesn't need to be public β€” it’s an implementation detail of Rectangle’s serialization.

3 Likes

Since we're talking about names now: What you have is a specific kind of rectangles known as a square. It'd be good if your struct is named that to make it's intent clearer.

Excellent suggestion! It gave me another idea: why not implement Serialize for the in-app representation by using the Shadow struct? Would make the API easier to use, the user doesn't have to know the serialised representation differs, and also doesn't have to call From/Into before serialisation.

That is exactly what #[serde(from = "RectangleShadow")], already in @attack68's code, does.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.