在基于另一个模式对象的 json 模式上使用条件语句

Use conditional statements on json schema based on another schema object

我有一个 json 对象,例如:

{
  "session": {
    "session_id": "A",
    "start_timestamp": 1535619633301
  },
  "sdk": {
    "name": "android",
    "version": "21"
  }
}

sdk name 可以是 android or iossession_id 是基于 sdk json 中的 name field。我写了一个json schema using conditional statement (Using draft 7) 如下:

但它以一种意想不到的方式工作:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$ref": "#/definitions/Base",
  "definitions": {
    "Base": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "session": {
          "$ref": "#/definitions/Session"
        },
        "sdk": {
          "$ref": "#/definitions/SDK"
        }
      },
      "title": "Base"
    },
    "Session": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "start_timestamp": {
          "type": "integer",
          "minimum": 0
        },
        "session_id": {
          "type": "string",
          "if": {
            "SDK": {
              "properties": {
                "name": {
                  "enum": "ios"
                }
              }
            }
          },
          "then": {
            "pattern": "A"
          },
          "else": {
            "pattern": "B"
          }
        }
      },
      "required": [
        "session_id",
        "start_timestamp"
      ],
      "title": "Session"
    },
    "SDK": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "version": {
          "type": "string"
        },
        "name": {
          "type": "string",
          "enum": [
            "ios",
            "android"
          ]
        }
      },
      "required": [
        "name",
        "version"
      ],
      "title": "SDK"
    }
  }
}

因此以下 JSON 通过:

{
      "session": {
        "session_id": "A",
        "start_timestamp": 1535619633301
      },
      "sdk": {
        "name": "ios",
        "version": "21"
      }
    }

但这失败了:

{
      "session": {
        "session_id": "B",
        "start_timestamp": 1535619633301
      },
      "sdk": {
        "name": "android",
        "version": "21"
      }
    }

有人可以解释一下吗?..即使这样也通过了:

{
      "session": {
        "session_id": "A",
        "start_timestamp": 1535619633301
      },
      "sdk": {
        "name": "android",
        "version": "21"
      }
    }

if 的值必须是 JSON 架构。如果您要使用 https://gist.github.com/Relequestual/f225c34f6becba09a2bcaa66205f47f3#file-schema-json-L29-L35 (29-35) 行并将其单独用作 JSON Schema,则不会施加任何验证约束,因为没有 JSON Schema 关键字对象的顶层。

{
  "SDK": {
    "properties": {
      "name": {
        "enum": "ios"
      }
    }
  }
}

规范中允许这样做,因为人们可能希望通过添加自己的关键字来扩展 JSON Schema 的功能。所以它是 "valid" JSON 架构,但实际上没有做任何事情。

您需要将 properties 添加到架构中才能使其有意义。

{
  "properties": {
    "SDK": {
      "properties": {
        "name": {
          "const": "ios"
        }
      }
    }
  }
}

另外,enum必须是一个数组。当你只有一个项目时,你可以使用 const.

我认为您遇到了与 中类似的问题。

@Relequestual 是正确的,因为您需要在 SDK 标注周围使用 properties 关键字。但是为了你想做的事情,你需要重新组织。

子模式仅在实例中的级别上运行,而不是在根级别上运行。

考虑这个模式,一个简单的 JSON 对象实例包含一个 one 和一个 two 属性:

{
  "properties": {
    "one": {
      "enum": ["yes", "no", "maybe"]
    },
    "two": {
      "if": {
        "properties": {
          "one": {"const": "yes"}
        }
      },
      "then": {
        ...       // do some assertions on the two property here
      },
      "else": {
        ...
      }
    }
  }
}

two属性下的if关键字只能考虑two属性下的部分实例(即two的价值)。它没有查看实例的根目录,因此根本看不到 one 属性。

要使 two 属性 子模式下的子模式可以在实例中看到 one 属性,您必须移动 ifproperties 关键字之外。

{
  "if": {
    "properties": {
      "one": {"const" : "yes"}
    }
  },
  "then": {
    ...       // do some assertions on the two property here
  },
  "else": {
    ...       // assert two here, or have another if/then/else structure to test the one property some more
  }
}

对于one的两个可能值,这已经很不错了。即使是三个可能的值也不错。但是,随着 one 的可能值增加,if 的嵌套也会增加,这会使您的架构难以阅读(并可能使验证速度变慢)。

我建议使用 anyOfoneOf,而不是使用 if/then/else 结构,其中每个子模式代表一个有效状态例如,给定 one.

的不同值
{
  "oneOf": [
    {
      "properties": {
        "one": {"const": "yes"},
        "two": ...         // do some assertions on the two property here
      }
    },
    {
      "properties": {
        "one": {"const": "no"},
        "two": ...         // do some assertions on the two property here
      }
    },
    {
      "properties": {
        "one": {"const": "maybe"},
        "two": ...         // do some assertions on the two property here
      }
    }
  ]
}

我认为这更干净。

希望该解释可以帮助您重建架构以允许其他实例通过。

您必须将条件移动到足够高的级别,以便能够引用它需要引用的所有属性。在这种情况下,这是 /definitions/Base 架构。然后你只需要按照 Relequestual 的解释正确地编写你的模式。

{
  "$ref": "#/definitions/Base",
  "definitions": {
    "Base": {
      "type": "object",
      "properties": {
        "session": { "$ref": "#/definitions/Session" },
        "sdk": { "$ref": "#/definitions/SDK" }
      },
      "allOf": [
        {
          "if": {
            "properties": {
              "sdk": {
                "properties": {
                  "name": { "const": "ios" }
                }
              }
            },
            "required": ["sdk"]
          },
          "then": {
            "properties": {
              "session": {
                "properties": {
                  "session_id": { "pattern": "A" }
                }
              }
            }
          },
          "else": {
            "properties": {
              "session": {
                "properties": {
                  "session_id": { "pattern": "B" }
                }
              }
            }
          }
        }
      ]
    },
  ...
}