Andrew Dobrich
Andrew Dobrich

Reputation: 23

Rust Serde - Is it possible to map json data the could be in two different layouts back to a single struct?

The data I'm trying to map to my struct comes in two formats:

With card_faces, when there is more than one face to the card:

{
  "object": "card",
  "id": "some_id",
  "lang": "en",
  "released_at": "2012-02-03",
  "card_faces": [
    {
      "name": "some_name",
      "cost": "5",
      "ctype": "some_type",
      "colors": [
        "R",
        "B"
      ]
    },
    {
      "name": "another_name",
      "cost": "",
      "ctype": "another_type",
      "colors": [
        "R",
        "B"
      ]
    }
  ],
  "set_code": "some_code"
}

And without card_faces, when there is only one face (the face fields are just placed in the root):

{
  "object": "card",
  "id": "some_id",
  "lang": "en",
  "released_at": "2012-02-03",
  "name": "some_name",
  "cost": "5",
  "ctype": "some_type",
  "colors": [
    "R",
    "B"
  ],
  "set_code": "some_code"
}

I would like my struct to always have a Vec<CardFace> field. Something like:

#[derive(Deserialize)]
struct Card {
  object: String,
  id: String,
  lang: String,
  released_at: String,
  faces: Vec<CardFace>,
  set_code: String,
}

Is it possible to deserialize these objects and coerce the face fields into the desired format or would I need to manipulate the json prior to deserializing?

Upvotes: 2

Views: 496

Answers (1)

mcarton
mcarton

Reputation: 30001

I would do this using an intermediary enum:

#[derive(Deserialize)]
#[serde(untagged)]
enum CardTmpDeser {
    Card {
        object: String,
        id: String,
        lang: String,
        released_at: String,
        card_faces: Vec<CardFace>,
        set_code: String,
    },
    SingleCard {
        object: String,
        id: String,
        lang: String,
        released_at: String,
        name: String,
        cost: String,
        ctype: String,
        colors: Vec<String>,
        set_code: String,
    },
}

With the #[serde(untagged)] attribute, you can transparently deserialize both kinds of data you have.

Now simply tag your actual structure with #[serde(from = "CardTmpDeser")]:

#[derive(Debug, Deserialize)]
#[serde(from = "CardTmpDeser")]
pub struct Card {
    object: String,
    id: String,
    lang: String,
    released_at: String,
    card_faces: Vec<CardFace>,
    set_code: String,
}

and implement From<CardTmpDeser> for Card and you're good to go! serde will automatically deserialize your data using CardTmpDeser but will transparently convert it to your final type.

(Permalink to the playground with full working example)

Upvotes: 4

Related Questions