【问题标题】:How do I require one field or another or (one of two others) but not all of them?我如何需要一个字段或另一个字段或(其他两个字段之一)但不是全部?
【发布时间】:2014-07-24 06:47:21
【问题描述】:

我无法提出一个 JSON 模式来验证 JSON 是否包含:

  • 只有一个字段
  • 仅其他字段
  • (仅其他两个字段之一)

但当存在多个时不匹配。

具体来说,我想要一个

  • copyAll
  • fileNames
  • matchesFiles 和/或 doesntMatchFiles

要验证,但我不想接受超过这个数字。

这是我目前所得到的:

{
    "$schema": "http://json-schema.org/draft-04/schema#",
    "type": "object",
    "required": [ "unrelatedA" ],
    "properties": {
    "unrelatedA": {
        "type": "string"
    },
    "fileNames": {
        "type": "array"
    },
    "copyAll": {
        "type": "boolean"
    },
    "matchesFiles": {
        "type": "array"
    },
    "doesntMatchFiles": {
        "type": "array"
        }
    },
    "oneOf": [
         {"required": ["copyAll"], "not":{"required":["matchesFiles"]}, "not":{"required":["doesntMatchFiles"]}, "not":{"required":["fileNames"]}},
         {"required": ["fileNames"], "not":{"required":["matchesFiles"]}, "not":{"required":["doesntMatchFiles"]}, "not":{"required":["copyAll"]}},
         {"anyOf": [
               {"required": ["matchesFiles"], "not":{"required":["copyAll"]}, "not":{"required":["fileNames"]}},
               {"required": ["doesntMatchFiles"], "not":{"required":["copyAll"]}, "not":{"required":["fileNames"]}}]}
    ]
} ;

这比我想要的更匹配。我希望它匹配以下所有内容:

{"copyAll": true, "unrelatedA":"xxx"}
{"fileNames": ["aab", "cab"], "unrelatedA":"xxx"}
{"matchesFiles": ["a*"], "unrelatedA":"xxx"}
{"doesntMatchFiles": ["a*"], "unrelatedA":"xxx"}
{"matchesFiles": ["a*"], "doesntMatchFiles": ["*b"], "unrelatedA":"xxx"}

但不匹配:

{"copyAll": true, "matchesFiles":["a*"], "unrelatedA":"xxx"}
{"fileNames": ["a"], "matchesFiles":["a*"], "unrelatedA":"xxx"}
{"copyAll": true, "doesntMatchFiles": ["*b"], "matchesFiles":["a*"], "unrelatedA":"xxx"}
{"fileNames": ["a"], "matchesFiles":["a*"], "unrelatedA":"xxx"}
{"unrelatedA":"xxx"}

我猜我缺少一些明显的东西 - 我想知道它是什么。

【问题讨论】:

标签: json validation jsonschema


【解决方案1】:

问题在于“非”语义。 “不需要”并不意味着“禁止包含”。这只是意味着您不必为了验证该架构而添加它。

但是,您可以使用“oneOf”以更简单的方式满足您的规范。请记住,这意味着“这些模式中只有一个可以验证”。以下架构实现了您尝试解决的属性切换:

{
    "$schema": "http://json-schema.org/draft-04/schema#",
    "type": "object",
    "required": [
        "unrelatedA"
    ],
    "properties": {
        "unrelatedA": {
            "type": "string"
        },
        "fileNames": {
            "type": "array"
        },
        "copyAll": {
            "type": "boolean"
        },
        "matchesFiles": {
            "type": "array"
        },
        "doesntMatchFiles": {
            "type": "array"
        }
    },
    "oneOf": [
        {
            "required": [
                "copyAll"
            ]
        },
        {
            "required": [
                "fileNames"
            ]
        },
        {
            "anyOf": [
                {
                    "required": [
                        "matchesFiles"
                    ]
                },
                {
                    "required": [
                        "doesntMatchFiles"
                    ]
                }
            ]
        }
    ]
}

【讨论】:

  • “不需要”如何不意味着禁止包含? IMO,如果您恰好有一个“不需要”元素,那么如果该元素存在,则“需要”成功,“不”因此失败。请注意,在 OP 的示例中,一个对象中有多个“非”键,这可能会导致一些奇怪的行为。此外,如果您在“不需要”子句中有多个元素,则仅表示只有 all 元素都存在时验证才会失败。然而,这可以通过使用“allOf”来规避。如果我错了,请纠正我。
  • @Tomeamis "not required" 的意思是"可能存在也可能不存在",而"inclusion denied" 的意思是"不得存在",不一样
  • @Mark 用简单的英语,你是对的。在 JSON 模式中,not 元素内的 required 元素(其数组内有一个字符串)实际上意味着 required 的参数是被禁止的。尝试针对此架构:{"$schema":"http://json-schema.org/draft-07/schema#","not":{"required":["forbidden"]}} 验证此 JSON:{"forbidden":"asd"},您将看到验证失败。
【解决方案2】:

如果值为null 的属性与它不存在一样好,那么这样的东西可能是合适的。必须提供commonProp,并且只能提供xy 之一。

您可能会收到一些类似的错误消息。

{
    $schema: 'http://json-schema.org/draft-07/schema#',
    type: 'object',
    required: ['commonProp'],

    oneOf: [
        {
            properties: {
                x: { type: 'number' },
                commonProp: { type: 'number' },
                y: {
                    type: 'null',
                    errorMessage: "should ONLY include either ('x') or ('y') keys. Not a mix.",
                },
            },
            additionalProperties: { not: true, errorMessage: 'remove additional property ${0#}' },
        },
        {
            properties: {
                y: { type: 'number' },
                commonProp: { type: 'number' },
                x: {
                    type: 'null',
                    errorMessage: "should ONLY include either ('x') or ('y') keys. Not a mix.",
                },
            },
            additionalProperties: { not: true, errorMessage: 'remove additional property ${0#}' },
        },
    ],
}
const model = { x: 0, y: 0, commonProp: 0 };

// ⛔️ ⛔️ ⛔️ ⛔️ ⛔️ ⛔️
// Model>y should ONLY include either ('x') or ('y') keys. Not a mix.
// Model>x should ONLY include either ('x') or ('y') keys. Not a mix.
const model = { x: 0, y: null, commonProp: 0 };

// ✅ ✅ ✅ ✅ ✅ ✅
const model = { x: 0 };

// ⛔️ ⛔️ ⛔️ ⛔️ ⛔️ ⛔️
// Model must have required property 'commonProp'

【讨论】:

    【解决方案3】:

    正如@Tomeamis 在 cmets 中指出的那样,不需要的组合在 json 模式中意味着“禁止”。但是,您不应该重复“not”关键字(我真的不知道为什么)。相反,你应该

    {
    "$schema": "http://json-schema.org/draft-04/schema#",
    "type": "object",
    "required": [ "unrelatedA" ],
    "properties": {
        "unrelatedA": {
            "type": "string"
        },
        "fileNames": {
            "type": "array"
        },
        "copyAll": {
            "type": "boolean"
        },
        "matchesFiles": {
            "type": "array"
        },
        "doesntMatchFiles": {
            "type": "array"
        }
    },
    "oneOf": [
         {
             "required": [
                 "copyAll"
             ],
             "not": {
                 "anyOf": [
                     {"required":["matchesFiles"]},
                     {"required":["doesntMatchFiles"]},
                     {"required":["fileNames"]}
                 ]
            }
         },
         {
             "required": [
                 "fileNames"
             ],
             "not": {
                 "anyOf": [
                     {"required":["matchesFiles"]},
                     {"required":["doesntMatchFiles"]},
                     {"required":["copyAll"]}
                 ]
            }
         },
         {
             "anyOf": [
               {
                   "required": ["matchesFiles"],
                   "not": {
                       "anyOf": [
                           {"required":["fileNames"]},
                           {"required":["copyAll"]}
                       ]
                   }
               },
               {
                   "required": ["doesntMatchFiles"],
                   "not": {
                       "anyOf": [
                           {"required":["fileNames"]},
                           {"required":["copyAll"]}
                       ]
                   }
               }]
         }
    ]
    }
    

    更多详情here

    要禁止属性的存在,也可以这样做

    {
        "properties": {
            "x": false
        }
    }
    

    如答案here中所述

    【讨论】:

      【解决方案4】:

      这个答案与 JSON 架构无关,所以它有点偏离轨道,虽然它可以带来解决这个问题的另一个视角,以及一般的 json 验证。

      关键是要以声明的方式准确地表达您所需要的结果:唯一存在的单个字段。考虑以下 json 模式:

      JsonElement json =
          new Gson().toJsonTree(
              Map.of(
                  "first_field", "vasya",
                  "second_field", false,
                  "third_field", 777,
                  "unrelated", "Rinse"
              )
          );
      

      假设您需要first_fieldsecond_fieldthird_field 之一。第四个字段无关紧要。下面是相应的验证对象的样子:

      Result<SomeTestStructure> result =
          new UnnamedBlocOfNameds<SomeTestStructure>(
              List.of(
                  new OneOf(
                      "global",
                      new ErrorStub("Only one of the fields must be present"),
                      new AsString(
                          new Required(
                              new IndexedValue("first_field", json)
                          )
                      ),
                      new AsBoolean(
                          new Required(
                              new IndexedValue("second_field", json)
                          )
                      ),
                      new AsInteger(
                          new Required(
                              new IndexedValue("third_field", json)
                          )
                      )
                  ),
                  new AsString(
                      new IndexedValue("unrelated", json)
                  )
              ),
              SomeTestStructure.class
          )
              .result();
      

      首先,您声明一个由命名块组成的未命名块;然后你说你需要三个元素中的一个成功的可验证元素。最后,你宣布成功意味着什么。在这种情况下,成功就是简单地存在。如果json有效,则创建SomeTestStructure类的对象:

      assertTrue(result.isSuccessful());
      assertEquals(
          new SomeTestStructure(777, "Rinse").thirdField(),
          result.value().raw().thirdField()
      );
      

      有关此方法和实现它的库的更多信息,请查看quick start entry

      【讨论】:

        猜你喜欢
        • 2017-09-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-10-24
        • 2021-05-29
        • 1970-01-01
        • 2012-03-12
        • 2022-10-15
        相关资源
        最近更新 更多