Home > Software engineering >  How to deserialize a json object into rust understandable code using enums?
How to deserialize a json object into rust understandable code using enums?

Time:01-07

I need to deserialize ( and later on serialize ) a piece of data that has this type of a structure :

{
  "type": "TypeApplication",
  "val": {
    "con": {
      "type": "TypeConstructor",
      "val": [
        "Builtin",
        "Record"
      ]
    },
    "arg": {
      "type": "RowCons",
      "val": {
        "label": "953e3dd6-826e-1985-cb99-fd4ed4da754e",
        "type": {
          "type": "TypeApplication",
          "val": {
            "con": {
              "type": "TypeConstructor",
              "val": [
                "Builtin",
                "List"
              ]
            },
            "arg": {
              "type": "Element",
              "meta": {
                "multiLine": true
              }
            }
          },
          "system": {
            "label": "nullam-senectus-port - Text",
            "isBindable": true,
            "defaultValue": [
              {
                "id": "4a05486f-f24d-45f8-ae13-ab05f824d74d",
                "type": "String",
                "pluginType": "Basic",
                "data": {
                  "value": "Nullam senectus porttitor in eget. Eget rutrum leo interdum."
                },
                "children": [],
                "text": true
              }
            ],
            "isUnlinked": false,
            "isDefault": false
          }
        },
        "tail": {
          "type": "RowCons",
          "val": {
            "label": "94f603df-d585-b45a-4252-9ec77cf5b13c",
            "type": {
              "type": "TypeApplication",
              "val": {
                "con": {
                  "type": "TypeConstructor",
                  "val": [
                    "Builtin",
                    "List"
                  ]
                },
                "arg": {
                  "type": "Element",
                  "meta": {
                    "multiLine": true
                  }
                }
              },
              "system": {
                "label": "best-services - Text",
                "isBindable": true,
                "defaultValue": [
                  {
                    "id": "6265ca45-3f69-4844-97e2-c05bbfb9fee5",
                    "type": "String",
                    "pluginType": "Basic",
                    "data": {
                      "value": "Best Services"
                    },
                    "children": [],
                    "text": true
                  }
                ]
              }
            },
            "tail": {
              "type": "RowEmpty"
            }
          }
        }
      }
    }
  }
}

I do not know what this data exactly is, but I know this is trying to represent a function/element that takes in values and defaults for those values as parameters/properties.

I want to deserialize it using serde and consequently serialize it too. I have so far been able to write something that sort of works but not really :

#[skip_serializing_none]
#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(tag = "type", content = "val")]
pub enum WebflowPropDataType {
    TypeApplication {
        con: Box<WebflowPropDataType>, // Normally Passes the Type Constructor
        arg: Box<WebflowPropDataType>, // Normally Passes the Row Constructor
    },
    TypeConstructor(Vec<String>), // Stores Value of TypeConstructor
    RowCons {
        label: String, // Stores the label of the Row
        #[serde(rename = "type")]
        row_con_type: Box<WebflowPropDataType>, // Stores the type of the Row
        tail: Box<WebflowPropDataType>,
    },
    RowEmpty, // For Ending the recursive usage of rowConstructor
}

#[skip_serializing_none]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct WebflowRowConDataType {
    #[serde(rename = "type")]
    val_type: String, // TypeApplication
    val: Box<WebflowPropDataType>,
} 

This works for a structure like this :

{
    "type": "TypeApplication",
    "val":{
        "con": {
            "type": "TypeConstructor",
            "val": []
          },       
        "arg": {
            "type": "RowEmpty"
        }
    }
}

but would fail if I try to work with initial example. I know this may be due to the lack of a proper arg type or maybe even the TypeApplication Enum hand is malformed.

I do see that a adjancent typing solution would be enough for most of the times but there are cases as seen in the example structure that there is a third value called system and I am unable to determine what type of approach would help me achieve this type of outcome.

How should I approach this problem in order to generate this type of code.

I am not asking anyone to write me a solution but to give me suggestion as to what my approach should be? Whether you'd know what type of data this is/how to generated this , or even if there are some other library I should look into to manipulate this type of data or maybe look at other approaches.

PS : - My end goal is to be able to generate / serialize this type of JSON code from already contained knowledge of properties and default values of a function/object.

CodePudding user response:

Here are my recommendations:

  1. Use just #[serde(tag = "type")] instead of #[serde(tag = "type", content = "val")]. You will have to handle val manually (extracting the current enum members into separate structs), but this allows you to also handle TypeApplication.system and Element.meta.

    This also has the small benefit of reducing the amount of Boxes involved.

  2. Consider whether all of the different cases in WebflowPropDataType can actually occur everywhere it's used. If not (maybe Element can only happen under TypeApplication.val.arg), then you may want to split the enum into multiple so that this is reflected in the type system.


Example for #1:

use serde::{Serialize, Deserialize};

#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct TypeApplicationVal {
    con: WebflowPropDataType, // Normally Passes the Type Constructor
    arg: WebflowPropDataType, // Normally Passes the Row Constructor
}

// #[skip_serializing_none]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct TypeApplicationSystem {
    label: String,
    #[serde(rename = "isBindable")]
    is_bindable: bool,

    // TODO: defaultValue

    #[serde(rename = "isUnlinked")]
    #[serde(skip_serializing_if = "Option::is_none")]
    is_unlinked: Option<bool>,
    #[serde(rename = "isDefault")]
    #[serde(skip_serializing_if = "Option::is_none")]
    is_default: Option<bool>,
}

#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct RowConsVal {
    label: String, // Stores the label of the Row
    #[serde(rename = "type")]
    typ: WebflowPropDataType, // Stores the type of the Row
    tail: WebflowPropDataType,
}

#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct ElementMeta {
    #[serde(rename = "multiLine")]
    multi_line: bool,
}

#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(tag = "type")]
pub enum WebflowPropDataType {
    TypeApplication {
        val: Box<TypeApplicationVal>,
        #[serde(skip_serializing_if = "Option::is_none")]
        system: Option<TypeApplicationSystem>,
    },
    TypeConstructor {
        val: Vec<String> // Stores Value of TypeConstructor
    },
    RowCons {
        val: Box<RowConsVal>,
    },
    Element {
        meta: ElementMeta,
    },
    RowEmpty, // For Ending the recursive usage of rowConstructor
}

playground

  • Related