Rust Serde - возможно ли отобразить json данных, которые могут быть в двух разных макетах, обратно в одну структуру? - PullRequest
2 голосов
/ 27 марта 2020

Данные, которые я пытаюсь отобразить в моей структуре, представлены в двух форматах:

С card_faces, когда на карте имеется более одного лица:

{
  "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"
}

И без card_faces, когда есть только одна грань (поля граней просто помещены в 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"
}

Я бы хотел, чтобы в моей структуре всегда было поле Vec<CardFace>. Что-то вроде:

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

Можно ли десериализовать эти объекты и привести поля лица в нужный формат или мне нужно будет манипулировать json до десериализации?

1 Ответ

4 голосов
/ 27 марта 2020

Я бы сделал это, используя посредника 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,
    },
}

С атрибутом #[serde(untagged)] вы можете прозрачно десериализовать оба типа данных, которые у вас есть.

Теперь просто пометите ваша фактическая структура с #[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,
}

и реализуйте From<CardTmpDeser> для Card, и вы хороши для go! serde автоматически десериализует ваши данные, используя CardTmpDeser, но прозрачно преобразует их в ваш окончательный тип.

( Постоянная ссылка на игровую площадку с полным рабочим примером )

...