Rust Serde-是否可以将可能处于两种不同布局的json数据映射回单个结构?

 收藏

我尝试映射到我的结构的数据有两种格式:

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,
}

是否可以反序列化这些对象并将面部字段强制转换为所需格式,或者在反序列化之前我需要操纵json吗?

回复
  • 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)