如何根据 JSON 模式中存在的其他属性有条件地禁止属性?

How to conditionally forbid properties based on presence of other properties in JSON Schema?

在我的架构中,我声明了这些属性:

"index_name": {
      "type": "string",
      "examples": ["foo-wwen-live", "foo"]
    },
"locale": {
      "type": "string",
      "examples": ["wwen", "usen", "frfr"]
},
"environment": {
      "type": "string",
      "default": "live",
      "examples": [
        "staging",
        "edgengram",
        "test"
      ]
}

我希望根据我的模式验证的 JSON 主体有效 仅当 :

简而言之,localeenvironment 绝不能与 index_name 混用。

测试用例和期望的结果:

这些应该通过:
案例#1

{
  "locale": "usen"
}

案例 #2

{
  "environment": "foo"
}

案例 #3

{
  "environment": "foo",
  "locale": "usen"
}

案例 #4

{
  "index_name": "foo-usen"
}

这些不应该通过:
案例 #5

{
  "index_name": "foo-usen",
  "locale": "usen"
}

案例 #6

{
  "index_name": "foo-usen",
  "environment": "foo"
}

案例 #7

{
  "index_name": "foo-usen",
  "locale": "usen",
  "environment": "foo"
}

我为我的架构创建了以下规则,但它并未涵盖所有情况。例如,如果 localeenvironment 都存在,则如果 index_name 也存在,则验证 returns 失败,根据案例 #7,这是正确的行为。但是,如果 localeenvironment 中只有一个存在,它允许 index_name 也存在(在案例 #5 和 #6 中失败)。

  "oneOf": [
    {
      "required": ["index_name"],
      "not": {"required":  ["locale", "environment"]}
    },
    {
      "anyOf": [
        {
          "required": ["locale"],
          "not": {"required": ["index_name"]}
        },
        {
          "required": ["environment"],
          "not": {"required": ["index_name"]}
        }
      ]
    }
  ]

关于 "not": {"required": []} 声明如何工作,我得到的信息很复杂。 claim this means that it forbids anything declared in the array to be present, in contrary to what idea does the syntax give. Other claim 这应该完全按照听起来的方式来理解:数组中列出的属性 不需要 - 它们可以存在,但如果它们不存在也没关系.

除此规则外,我还要求在所有情况下都存在一个不相关的 属性,并且我设置了 "additionalProperties": false

满足我所有测试用例的规则是什么?

因为 required: [a, b] 意味着(a 必须存在且 b 必须存在)

然后 not: {required: [a, b]} 表示(不(a 必须存在且 b 必须存在))

这在逻辑上等同于(a 不得存在或 b 不得存在)。

所以这不是正确的表达方式(a 不得存在且 b 不得存在)。你需要两个 nots.

根据您的要求,这是正确的表达方式:

{
  "oneOf": [
    {
      "required": ["index_name"],
      "allOf": [
        {"not": {"required": ["locale"]}},
        {"not": {"required": ["environment"]}}
      ]
    },
    {
      "anyOf": [
        {"required": ["locale"]},
        {"required": ["environment"]}
      ],
      "not": {
        "required": ["index_name"]
      }
    }
  ]
}

依赖关系

这是 dependencies 关键字的工作。下面说

  • 如果 "locale" 存在,则 "index_name" 被禁止。
  • 如果 "environment" 存在,则 "index_name" 被禁止。

|

"dependencies": {
  "locale": { "not": { "required": ["index_name"] } },
  "environment": { "not": { "required": ["index_name"] } }
}

not-required怎么了?

有一个关于 not-required 如何工作的子问题。这令人困惑,因为它并不意味着它在英语中的读法,但它的相似程度足以让我们有时认为它确实如此。

在上面的例子中,如果我们把它读成"not required",听起来就像是"optional"的意思。更准确的描述是 "forbidden".

这很尴尬,但还算不错。当您想要 "forbid" 多个 属性 时,就会让人感到困惑。假设我们想说,如果 "foo" 存在,则 "bar" 和 "baz" 被禁止。您可能首先尝试的是这个。

"dependencies": {
  "foo": { "not": { "required": ["bar", "baz"] } }
}

然而,这意味着如果 "foo" 存在,则实例无效,如果 "bar" AND "baz" 是当前的。他们都必须在那里触发失败。我们真正想要的是如果存在 "bar" "baz" 则它无效。

"dependencies": {
  "foo": {
    "not": {
      "anyOf": [
        { "required": ["bar"] },
        { "required": ["baz"] }
      ]
    }
  }
}

为什么这么难?

JSON 架构针对可容忍更改的架构进行了优化。模式应强制实例具有完成特定任务所需的数据。如果超过它的需要,应用程序将忽略其余部分。这样,如果将某些东西添加到实例中,一切仍然有效。如果实例有一些应用程序不使用的额外字段,它不应该验证失败。

因此,当您尝试执行类似禁止您原本可以忽略的事情时,您有点违背 JSON 架构的原则,事情可能会变得有点丑陋。然而,有时这是必要的。我对你的情况了解不多,无法打那个电话,但我猜想 dependencies 在这种情况下可能是必要的,但 additionalProperties 不是。