Десериализовать переменный мета-объект с помощью serde - PullRequest
1 голос
/ 07 мая 2020

Я ищу элегантный способ десериализации следующего ввода:

{
  "products": [

    {
      "id": 1,
      "ptype": "Clothes",
      "description": "some data about clothes",
      "metadata": {
        "colors" : ["blue", "green"],
        "web": false,
        "size": 2
      }
    },
    {
      "id": 4,
      "ptype": "Food",
      "description": "text for foods",
      "metadata": {
        "country": "France",
        "wine": true
      }
    },
    {
      "id": 12,
      "ptype": "EmptyPlaceholder",
      "description": "nothing at all",
      "metadata": {
      }
    }
  ]
}

json содержит массив продуктов. Продукт можно идентифицировать по полю ptype. Объект метаданных различается в зависимости от типа поля. Например, если ptype - Food, то метаданными для еды будут строка (страна) и логическое значение (вино). Таким образом, у продуктов есть несколько общих полей: id, ptype и description, а также некоторые метаданные. Я хочу десериализовать этот JSON файл в Vec<Product>.

До сих пор я использовал следующий код:

use serde::{Deserialize};
use serde_json::Result;

#[derive(Deserialize, Debug)]
struct ClothesData {
    colors : Vec<String>,
    web : bool,
    size: u32,
}

#[derive(Deserialize, Debug)]
struct FoodData {
    country: String,
    wine: bool,
}

#[derive(Deserialize, Debug)]
struct EmptyData {

}

#[derive(Deserialize, Debug)]
enum Metadata {
    ClothesData,
    FoodData,
    EmptyData,
}

#[derive(Deserialize, Debug)]
enum  Ptype {
    Clothes,
    Food,
    EmptyPlaceholder
}

#[derive(Deserialize, Debug)]
struct Product {
    id: u32,
    ptype: Ptype,
    description: Option<String>,
    metadata: Metadata,

}

Я не уверен, что делать дальше, и я хотел бы спросить, может ли serde crate делать это «автоматически».

1 Ответ

1 голос
/ 07 мая 2020

Это соответствует "смежно помеченным" представлению перечисления serde:

use serde::Deserialize;
use serde_json::Result;

#[derive(Deserialize, Debug)]
struct Clothes {
    colors: Vec<String>,
    web: bool,
    size: u32,
}

#[derive(Deserialize, Debug)]
struct Food {
    country: String,
    wine: bool,
}

#[derive(Deserialize, Debug)]
struct Empty {}

#[derive(Deserialize, Debug)]
#[serde(tag = "ptype", content = "metadata")]
enum Kind {
    Clothes(Clothes),
    Food(Food),
    EmptyPlaceholder(Empty),
}

#[derive(Deserialize, Debug)]
struct Product {
    id: u32,
    description: Option<String>,
    #[serde(flatten)]
    kind: Kind,
}

#[derive(Deserialize, Debug)]
struct Root {
    products: Vec<Product>,
}

fn main() -> Result<()> {
    let data = r#"
    {
        "products": [

            {
              "id": 1,
              "ptype": "Clothes",
              "description": "some data about clothes",
              "metadata": {
                "colors" : ["blue", "green"],
                "web": false,
                "size": 2
              }
            },
            {
              "id": 4,
              "ptype": "Food",
              "description": "text for foods",
              "metadata": {
                "country": "France",
                "wine": true
              }
            },
            {
              "id": 12,
              "ptype": "EmptyPlaceholder",
              "description": "nothing at all",
              "metadata": {
              }
            }
        ]
    }"#;

    let root: Root = serde_json::from_str(data)?;
    println!("{:#?}", root);

    Ok(())
}
...