【问题标题】:How to validate a JSON object against a JSON schema based on object's type described by a field?如何根据字段描述的对象类型针对 JSON 模式验证 JSON 对象?
【发布时间】:2018-04-13 18:53:31
【问题描述】:

我需要根据 JSON 模式 (draft-04) 验证许多对象(消息)。每个对象都保证有一个“类型”字段,用于描述其类型,但每种类型都有一组完全不同的其他字段,因此每种类型的对象都需要一个唯一的模式。

我看到了几种可能性,但没有一个特别吸引人,但我希望我遗漏了一些东西。

可能性 1:对每种消息类型使用 oneOf。我想这会起作用,但问题是很长的验证错误,以防出现问题:验证器倾向于报告每个失败的模式,其中包括“oneOf”数组中的所有元素。

{
  "oneOf":
  [
    {
      "type": "object",
      "properties":
      {
        "t":
        {
          "type": "string",
          "enum":
          [
            "message_type_1"
          ]
        }
      }
    },
    {
      "type": "object",
      "properties":
      {
        "t":
        {
          "type": "string",
          "enum":
          [
            "message_type_2"
          ]
        },
        "some_other_property":
        {
          "type": "integer"
        }
      },
      "required":
      [
        "some_other_property"
      ]
    }
  ]
}

可能性2:嵌套“if”、“then”、“else”三元组。我没有尝试过,但我想在这种情况下错误可能会更好。但是写起来很麻烦,因为嵌套if的堆积。

可能性 3:为“t”的每个可能值提供单独的方案。这是最简单的解决方案,但我不喜欢它,因为它使我无法在架构中使用公共元素(通过引用)。

那么,这些是我唯一的选择,还是我可以做得更好?

【问题讨论】:

  • 选项 3 并不妨碍您使用参考。引用另一个模式文件的一部分是完全有效和可能的。并不是说这是最好的选择。
  • 我认为选项 1 是您最好的选择。如果验证器不满足数组中的任何模式,则验证器有权报告来自oneOf 的所有错误。您是否希望能够将任何错误消息发回给用户以获取验证反馈?
  • 我希望用户——实际上是开发人员或测试人员——能够快速查明问题。至少会有几十种消息类型,并且得到一个列出所有这些类型的巨大错误并不完全有助于实现该目标。我越来越多地考虑选项3。你说得对,我仍然可以使用 refs,但我必须为每个消息模式加载包含该 ref 的文件。不理想,但可能必须这样做。
  • 同意。是的,如果库不支持文件 URI 协议(有些支持,但未定义行为),则必须将它们加载到库中。当您需要进行验证时,执行多个 HTTP 请求不太理想!

标签: jsonschema


【解决方案1】:

由于“type”是 JSON Schema 关键字,为了清楚起见,我将按照您的指示使用“t”作为类型区分字段。

没有特定的关键字来完成或表明这一点(但是,请参阅https://github.com/json-schema-org/json-schema-spec/issues/31 进行讨论)。这是因为,出于验证的目的,您需要做的一切都已经成为可能。在 JSON Schema 中,错误是次要的验证。我们所做的只是限制我们看到的错误数量,因为很明显存在错误不再有效的时候。

通常在验证消息时,您首先知道它的类型,然后再阅读消息的其余部分。例如在 HTTP 中,如果您正在读取以 Date: 开头的行并且下一个字符不是数字或字母,您可以立即发出错误(例如“意外的波浪号,预期的月份名称”)。

但在 JSON 中,情况并非如此,因为属性是无序的,并且您可能直到最后都不会遇到“t”,如果有的话。 "if/then" 可以帮助解决这个问题。

但首先,首先要分解出最重要的约束,然后将它们移到顶部。

首先,在您的顶级架构中使用 "type": "object""required":["t"],因为在所有情况下都是如此。

其次,使用“properties”和“enum”枚举其所有有效值。这样,如果“t”确实输入错误,那将是顶级架构的错误,而不是子架构。

如果所有这些约束都通过了,但文档仍然无效,那么更容易断定问题一定出在消息的其他内容上,而不是“t”属性本身。

现在在每个子模式中,使用"const" 将子模式与类型名称匹配。

我们得到这样的架构:

{
  "type": "object",
  "required": ["t"],
  "properties": { "t": { "enum": ["message_type_1", "message_type_2"] } }
  "oneOf": [
     {
        "type": "object",
        "properties": {
          "t": { "const": "message_type_1" }
        }
     },
     {
        "type": "object",
        "properties": 
          "t": { "const": "message_type_2" },
          "some_other_property": {
             "type": "integer"
          }
        },
        "required": [ "some_other_property" ]
     }
  ]
}

现在,将每种类型拆分为不同的架构文件。通过在“t”之后命名文件来使其易于访问。这样,应用程序可以读取对象流并选择架构来验证每个对象。

{
  "type": "object",
  "required": ["t"],
  "properties": { "t": { "enum": ["message_type_1", "message_type_2"] } }
  "oneOf": [
     {"$ref": "message_type_1.json"},
     {"$ref": "message_type_2.json"}
  ]
}

理论上,验证器现在有足够的信息来产生更清晰的错误(尽管我不知道有任何验证器可以做到这一点)。

因此,如果这不能为您生成足够清晰的错误报告,您有两种选择:

首先,您可以自己实施部分验证过程。如上所述,使用像 Oboe.js 这样的流式 JSON 解析器来读取流中的每个对象,解析对象并读取“t”属性,然后应用适当的架构。

或者其次,如果您真的想纯粹在 JSON Schema 中执行此操作,请在“allOf”中使用“if/then”语句:

{
  "type": "object",
  "required": ["t"],
  "properties": { "t": { "enum": ["message_type_1", "message_type_2"] } }
  "allOf": [
     {"if":{"properties":{"t":{"const":"message_type_1"}}}, "then":{"$ref": "message_type_1.json"}},
     {"if":{"properties":{"t":{"const":"message_type_2"}}}, "then":{"$ref": "message_type_2.json"}}
  ]
}

这应该会产生以下错误:

t 不是“message_type_1”或“message_type_2”之一

(因为t="message_type_2")some_other_property 不是整数

而不是两者兼而有之。

【讨论】:

  • 感谢您的详细解答。我会接受它,虽然我现在没有时间真正测试它,因为我已经用一些技巧为不同类型实现了单独的模式,但我可能想在未来的某个时候回到这个问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-18
  • 2014-02-11
  • 1970-01-01
  • 1970-01-01
  • 2018-03-03
  • 1970-01-01
相关资源
最近更新 更多