0

我正在为 Swift 中的 JSON 表单构建一个自定义渲染器,以与用于我们基于 Web 的应用程序的 JSON 表单架构集成。如果你不熟悉,两个 JSON 文件用于生成 UI 表单组件并链接到后端。下面是 JSON Schema 和 JSON UI Schema 的示例。JSON Schema 提供有关 UI 模式的数据类型的信息。然后为了链接两者,范围提供了 JSON Schema 中相关元素的路径。在一个非常简单的模式中,它并不算太糟糕,但是如果在 JSON 中有很多嵌套,它就会变得有点傻。我在下面有我的代码,用于通过将范围拆分为其组件然后挖掘字典来访问数据元素。有没有更好、更清洁、更少大锤的方法来做到这一点?我怀疑那里' 在这种情况下会有更深的嵌套数据,但以防万一只使用一个递归函数来查看每一层而不必自己做。我确定它存在,我只是没有看到它。

在你问“为什么要这样做?”之前 答案是那是黄铜交给我的任务,所以……我必须!

JSON模式:

{
  "type": "person",
  "required": [
    "age"
  ],
  "properties": {
    "firstName": {
      "type": "string",
      "minLength": 2,
      "maxLength": 20
    },
    "lastName": {
      "type": "string",
      "minLength": 5,
      "maxLength": 15
    },
    "age": {
      "type": "integer",
      "minimum": 18,
      "maximum": 100
    },
    "gender": {
      "type": "string",
      "enum": [
        "Male",
        "Female",
        "Undisclosed"
      ]
    },
    "height": {
      "type": "number"
    },
    "dateOfBirth": {
      "type": "string",
      "format": "date"
    },
    "rating": {
      "type": "integer"
    },
    "committer": {
      "type": "boolean"
    },
    "address": {
      "type": "object",
      "properties": {
        "street": {
          "type": "string"
        },
        "streetnumber": {
          "type": "string"
        },
        "postalCode": {
          "type": "string"
        },
        "city": {
          "type": "string"
        }
      }
    }
  }
}

JSON UI 架构:

{
  "type": "VerticalLayout",
  "elements": [
    {
      "type": "HorizontalLayout",
      "elements": [
        {
          "type": "Control",
          "scope": "#/properties/firstName"
        },
        {
          "type": "Control",
          "scope": "#/properties/lastName"
        }
      ]
    },
    {
      "type": "HorizontalLayout",
      "elements": [
        {
          "type": "Control",
          "scope": "#/properties/age"
        },
        {
          "type": "Control",
          "scope": "#/properties/dateOfBirth"
        }
      ]
    },
    {
      "type": "HorizontalLayout",
      "elements": [
        {
          "type": "Control",
          "scope": "#/properties/height"
        },
        {
          "type": "Control",
          "scope": "#/properties/gender"
        },
        {
          "type": "Control",
          "scope": "#/properties/committer"
        }
      ]
    },
    {
      "type": "Group",
      "label": "Address for Shipping T-Shirt",
      "elements": [
        {
          "type": "HorizontalLayout",
          "elements": [
            {
              "type": "Control",
              "scope": "#/properties/address/properties/street"
            },
            {
              "type": "Control",
              "scope": "#/properties/address/properties/streetnumber"
            }
          ]
        },
        {
          "type": "HorizontalLayout",
          "elements": [
            {
              "type": "Control",
              "scope": "#/properties/address/properties/postalCode"
            },
            {
              "type": "Control",
              "scope": "#/properties/address/properties/city"
            }
          ]
        }
      ],
      "rule": {
        "effect": "ENABLE",
        "condition": {
          "scope": "#/properties/committer",
          "schema": {
            "const": true
          }
        }
      }
    }
  ]
}

快速的蛮力方法:

static private func mapJSONSchemaToUISchema(_ uiSchema: JSONUISchemaElement)->JSONSchemaElement? {
    guard let scope = uiSchema.scope else {
        return nil
    }
    // separate out the scope string into components by /, starts with #, then properties, then the name of the JSONSchema element, then if it's nested, it will repeat properties and JSONSchemaElement, with the last element always being the element we would want.  There's a more elegant way to do this, however, this is the brute force for now.
    let scopeComponentPath = scope.components(separatedBy: "/")
    if scopeComponentPath.count < 3 {
        return nil
    } else {
        let element = getJSONElementFromPath(scopeComponentPath)
        return  element
    }
}

//iterrates through the JSON Schema to find the correct element based on the path (parsed from the UI Scope) to return the JSON Schema element, even if it's nested up to 5 layers deep.  If we continue to nest beyond 5 layers, this will require addressing.
static private func getJSONElementFromPath(_ stringsPath: [String]) ->JSONSchemaElement? {
    print(stringsPath)
    switch stringsPath.count {
    case 3:
        // not nested
        return jsonSchemaRootElement.properties?[stringsPath[2]]
    case 5:
        // nested in first level object
        return jsonSchemaRootElement.properties?[stringsPath[2]]?.properties?[stringsPath[4]]
    case  7:
        // nested in second level object
        return jsonSchemaRootElement.properties?[stringsPath[2]]?.properties?[stringsPath[4]]?.properties?[stringsPath[6]]
    case 9:
        // nested in third level object
        return jsonSchemaRootElement.properties?[stringsPath[2]]?.properties?[stringsPath[4]]?.properties?[stringsPath[6]]?.properties?[stringsPath[8]]
    case 11:
        // nested in fourth level object
        return jsonSchemaRootElement.properties?[stringsPath[2]]?.properties?[stringsPath[4]]?.properties?[stringsPath[6]]?.properties?[stringsPath[8]]?.properties?[stringsPath[10]]
    case 13:
        // nested in fifth level object
        return jsonSchemaRootElement.properties?[stringsPath[2]]?.properties?[stringsPath[4]]?.properties?[stringsPath[6]]?.properties?[stringsPath[8]]?.properties?[stringsPath[10]]?.properties?[stringsPath[12]]
    case 15:
        // nested in sixth level object
        return jsonSchemaRootElement.properties?[stringsPath[2]]?.properties?[stringsPath[4]]?.properties?[stringsPath[6]]?.properties?[stringsPath[8]]?.properties?[stringsPath[10]]?.properties?[stringsPath[12]]?.properties?[stringsPath[14]]
    default:
        print("no elements found in personJSONSchema array")
        return nil
    }
}
4

0 回答 0