如何根据字段描述的对象类型根据 JSON 模式验证 JSON 对象?
How to validate a JSON object against a JSON schema based on object's type described by a field?
我有许多对象(消息)需要根据 JSON 模式(draft-04)进行验证。每个对象都保证有一个 "type" 字段,它描述了它的类型,但是每个类型都有一组完全不同的其他字段,所以每个类型的对象都需要一个唯一的模式。
我看到了几种可能性,none 其中特别有吸引力,但我希望我遗漏了一些东西。
可能性 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" 的每个可能值的单独方案。这是最简单的解决方案,但我不喜欢它,因为它使我无法在模式中使用公共元素(通过引用)。
那么,这些是我唯一的选择吗,还是我可以做得更好?
由于 "type" 是一个 JSON 架构关键字,为了清楚起见,我将按照您的指示使用 "t" 作为类型区分字段。
没有特定的关键字来完成或指示这一点(但是,请参阅 https://github.com/json-schema-org/json-schema-spec/issues/31 进行讨论)。这是因为,为了验证的目的,您需要做的一切都已经成为可能。 JSON 模式中的错误是次要的验证。我们试图做的就是限制我们看到的错误数量,因为很明显存在一个错误不再有效的点。
通常当您验证消息时,您首先知道它的类型,然后阅读消息的其余部分。例如在 HTTP 中,如果您正在读取以 Date:
开头的行并且下一个字符不是数字或字母,您可以立即发出错误(例如 "Unexpected tilde, expected a month name")。
但是在 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 架构中执行此操作,请在 "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
not one of "message_type_1" or "message_type_2"
或
(because t
="message_type_2") some_other_property
not an integer
而不是两者。
我有许多对象(消息)需要根据 JSON 模式(draft-04)进行验证。每个对象都保证有一个 "type" 字段,它描述了它的类型,但是每个类型都有一组完全不同的其他字段,所以每个类型的对象都需要一个唯一的模式。
我看到了几种可能性,none 其中特别有吸引力,但我希望我遗漏了一些东西。
可能性 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" 的每个可能值的单独方案。这是最简单的解决方案,但我不喜欢它,因为它使我无法在模式中使用公共元素(通过引用)。
那么,这些是我唯一的选择吗,还是我可以做得更好?
由于 "type" 是一个 JSON 架构关键字,为了清楚起见,我将按照您的指示使用 "t" 作为类型区分字段。
没有特定的关键字来完成或指示这一点(但是,请参阅 https://github.com/json-schema-org/json-schema-spec/issues/31 进行讨论)。这是因为,为了验证的目的,您需要做的一切都已经成为可能。 JSON 模式中的错误是次要的验证。我们试图做的就是限制我们看到的错误数量,因为很明显存在一个错误不再有效的点。
通常当您验证消息时,您首先知道它的类型,然后阅读消息的其余部分。例如在 HTTP 中,如果您正在读取以 Date:
开头的行并且下一个字符不是数字或字母,您可以立即发出错误(例如 "Unexpected tilde, expected a month name")。
但是在 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 架构中执行此操作,请在 "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
not one of "message_type_1" or "message_type_2"
或
(because
t
="message_type_2")some_other_property
not an integer
而不是两者。