为什么我的 JSON 架构似乎没有正确验证子对象?
Why does my JSON Schema appear to not validate a child object correctly?
鉴于下面的 JSON 和架构,actor.mbox、actor.member[0].objectType 和 actor.member[0].mbox 都应该失败。他们不。我的架构中一定有问题。我想我已经将其缩小到处理 IdGroup 定义的范围,但我找不到问题所在。任何 json 架构大师看到任何明显错误的地方吗?
JSON
{
"actor": {
"objectType": "Group",
"name": "Group Identified",
"mbox": "http://should.fail.com",
"member": [
{
"objectType": "Agent_shouldfail",
"name": "xAPI mbox",
"mbox": "mailto:shouldfail"
}
]
},
"verb": {
"id": "http://adlnet.gov/expapi/verbs/attended",
"display": {
"en-GB": "attended",
"en-US": "attended"
}
},
"object": {
"objectType": "Activity",
"id": "http://www.example.com/meetings/occurances/34534"
}
}
JSON 架构(精简)
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "xAPIValidator",
"description": "Validation schema for xAPI tests",
"type": "object",
"allOf": [
{
"$ref": "#/definitions/Statement"
}
],
"statements": {
"type": "array",
"items": {
"allOf": [
{
"$ref": "#/definitions/Statement"
}
]
}
},
"definitions": {
"Statement": {
"$id": "#Statement",
"additionalProperties": false,
"properties": {
"objectType": {
"type:": "string",
"enum": [
"Agent",
"Activity",
"Group",
"SubStatement",
"StatementRef"
]
},
"id": {
"allOf": [
{
"$ref": "#/definitions/uuid"
}
]
},
"timestamp": {
"allOf": [
{
"$ref": "#/definitions/timestamp"
}
]
},
"stored": {
"allOf": [
{
"$ref": "#/definitions/timestamp"
}
]
},
"version": {
"allOf": [
{
"$ref": "#/definitions/semanticVersion"
}
]
},
"actor": {
"$id": "#actor",
"allOf": [
{
"$ref": "#/definitions/allOfAgentGroup"
}
]
},
"authority": {
"allOf": [
{
"$ref": "#/definitions/allOfAgentGroup"
}
]
},
"verb": {
"$id": "#verb",
"type": "object",
"properties": {
"id": {
"allOf": [
{
"$ref": "#/definitions/URI"
}
]
},
"display": {
"type": "object",
"allOf": [
{
"$ref": "#/definitions/lang5646"
}
]
}
}
},
"object": {
"$id": "#object",
"type": "object",
"additionalProperties": true,
"properties": {
"objectType": {
"type:": "string",
"enum": [
"Activity",
"Agent",
"Group",
"SubStatement",
"StatementRef"
]
}
}
}
},
"required": [
"actor",
"verb",
"object"
]
},
"attachment": {
"properties": {
"usageType": {
"allOf": [
{
"$ref": "#/definitions/URI"
}
]
},
"display": {
"allOf": [
{
"$ref": "#/definitions/lang5646"
}
]
},
"description": {
"allOf": [
{
"$ref": "#/definitions/lang5646"
}
]
},
"contentType": {
"type": "string",
"pattern": "\w+/[-+.\w]+;?(\w+.*=\w+;?)*"
},
"length": {
"type": "integer"
},
"sha2": {
"type": "string"
},
"fileUrl": {
"allOf": [
{
"$ref": "#/definitions/URI"
}
]
}
}
},
"semanticVersion": {
"type": [
"string"
],
"pattern": "^([0-9]+)\.([0-9]+)\\.([0-9]+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+)?$"
},
"Agent": {
"$id": "#Agent",
"allOf": [
{
"$ref": "#/definitions/IFI"
}
]
},
"AnonGroup": {
"$id": "#AnonGroup",
"maxProperties": 3,
"properties": {
"member": {
"type": "array",
"items": [
{
"allOf": [
{
"$ref": "#/definitions/allOfAgentGroup"
}
]
}
]
}
},
"dependencies": {
"objectType": [
"member"
]
},
"required": [
"member"
],
"not": {
"required": [
"mbox"
]
},
"not": {
"required": [
"mbox_sha1sum"
]
},
"not": {
"required": [
"openid"
]
},
"not": {
"required": [
"account"
]
}
},
"IdGroup": {
"$id": "#IdGroup",
"properties": {
"member": {
"type": "array",
"items": [
{
"allOf": [
{
"$ref": "#/definitions/allOfAgentGroup"
}
]
}
]
}
},
"allOf": [
{
"$ref": "#/definitions/IFI"
}
]
},
"allOfAgentGroup": {
"properties": {
"objectType": {
"type": "string",
"enum": [
"Agent",
"Group"
]
},
"name": {
"type": "string"
}
},
"oneOf": [
{
"if": {
"properties": {
"objectType": {
"const": "Agent"
}
}
},
"then": {
"allOf": [
{
"$ref": "#/definitions/Agent"
}
]
}
},
{
"if": {
"properties": {
"objectType": {
"const": "Group"
}
}
},
"then": {
"oneOf": [
{
"allOf": [
{
"$ref": "#/definitions/IdGroup"
}
]
},
{
"allOf": [
{
"$ref": "#/definitions/AnonGroup"
}
]
}
]
}
}
]
},
"IFI": {
"oneOf": [
{
"properties": {
"mbox": {
"allOf": [
{
"$ref": "#/definitions/mailto"
}
]
}
},
"required": [
"mbox"
]
},
{
"properties": {
"mbox_sha1sum": {
"type": "string",
"pattern": "\b[0-9a-f]{5,40}\b"
}
},
"required": [
"mbox_sha1sum"
]
},
{
"properties": {
"account": {
"properties": {
"homePage": {
"allOf": [
{
"$ref": "#/definitions/URI"
}
]
},
"name": {
"type": "string"
}
},
"required": [
"homePage",
"name"
]
}
},
"required": [
"account"
]
},
{
"properties": {
"openid": {
"allOf": [
{
"$ref": "#/definitions/URI"
}
]
}
},
"required": [
"openid"
]
}
]
},
"mailto": {
"type": "string",
"pattern": "(mailto:)(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])"
},
"timestamp": {
"type": "string",
"pattern": "^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(.[0-9]+)?(Z)?$"
},
"URI": {
"type": "string",
"pattern": "^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]"
},
"uuid": {
"type": "string",
"pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
},
"lang5646": {
"type": "object",
"patternProperties": {
"^((?:(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)|(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang))|((?:([A-Za-z]{2,3}(-(?:[A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-(?:[A-Za-z]{4}))?(-(?:[A-Za-z]{2}|[0-9]{3}))?(-(?:[A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-(?:[0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(?:x(-[A-Za-z0-9]{1,8})+))?)|(?:x(-[A-Za-z0-9]{1,8})+))$": {
"type": "string"
}
},
"additionalProperties": false
},
"lang5646string": {
"type": "string",
"pattern": "^((?:(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)|(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang))|((?:([A-Za-z]{2,3}(-(?:[A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-(?:[A-Za-z]{4}))?(-(?:[A-Za-z]{2}|[0-9]{3}))?(-(?:[A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-(?:[0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(?:x(-[A-Za-z0-9]{1,8})+))?)|(?:x(-[A-Za-z0-9]{1,8})+))$"
}
}
}
if/then/else
没有达到您的预期。
在您的模式定义中 allOfAgentGroup
有一个 oneOf
部分。
让我们单独看一下。
在您应该失败的示例数据中,让我们也单独使用 "actor" 对象。
架构:
{
"oneOf": [
{
"if": {
"properties": {
"objectType": {
"const": "Agent"
}
}
},
"then": false
}
},
false
]
}
实例数据:
{
"objectType": "Group",
"name": "Group Identified",
"mbox": "http://should.fail.com",
"member": [
{
"objectType": "Agent_shouldfail",
"name": "xAPI mbox",
"mbox": "mailto:shouldfail"
}
]
}
我们知道您希望 oneOf
数组中的第二部分项目无法通过验证。
出于调试和演示目的,我们假设它确实失败了,并将其更改为 false
(这是一个有效的 "JSON Schema",总是导致该分支上的验证失败)。
现在,鉴于上述架构和实例,您会期望 JSON 实例数据无法通过验证,对吗?
它没有,实际上验证通过了。
请记住,每个 JSON 架构关键字都会为您的验证要求添加约束。
让我们看看 if
和 then
实际上做了什么。
if
This validation outcome of this keyword's subschema has no direct
effect on the overall validation result. Rather, it controls which of
the "then" or "else" keywords are evaluated.
Instances that successfully validate against this keyword's subschema
MUST also be valid against the subschema value of the "then" keyword,
if present.
http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.6
好的,如果 if
的架构成功验证,则会应用 then
的架构。
实例数据未通过 if 条件验证,因此...未应用来自 then
的模式,最终得到的是有效的 "empty schema",这意味着没有验证oneOf/0
的限制条件。这导致您 oneOf
断言验证成功,因为 oneOf/0
通过而 oneOf/1
失败。
要解决此问题,您需要将 "then": false
添加到包含 if
和 then
的对象中,如果 [= =17=]条件不满足。
鉴于下面的 JSON 和架构,actor.mbox、actor.member[0].objectType 和 actor.member[0].mbox 都应该失败。他们不。我的架构中一定有问题。我想我已经将其缩小到处理 IdGroup 定义的范围,但我找不到问题所在。任何 json 架构大师看到任何明显错误的地方吗?
JSON
{
"actor": {
"objectType": "Group",
"name": "Group Identified",
"mbox": "http://should.fail.com",
"member": [
{
"objectType": "Agent_shouldfail",
"name": "xAPI mbox",
"mbox": "mailto:shouldfail"
}
]
},
"verb": {
"id": "http://adlnet.gov/expapi/verbs/attended",
"display": {
"en-GB": "attended",
"en-US": "attended"
}
},
"object": {
"objectType": "Activity",
"id": "http://www.example.com/meetings/occurances/34534"
}
}
JSON 架构(精简)
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "xAPIValidator",
"description": "Validation schema for xAPI tests",
"type": "object",
"allOf": [
{
"$ref": "#/definitions/Statement"
}
],
"statements": {
"type": "array",
"items": {
"allOf": [
{
"$ref": "#/definitions/Statement"
}
]
}
},
"definitions": {
"Statement": {
"$id": "#Statement",
"additionalProperties": false,
"properties": {
"objectType": {
"type:": "string",
"enum": [
"Agent",
"Activity",
"Group",
"SubStatement",
"StatementRef"
]
},
"id": {
"allOf": [
{
"$ref": "#/definitions/uuid"
}
]
},
"timestamp": {
"allOf": [
{
"$ref": "#/definitions/timestamp"
}
]
},
"stored": {
"allOf": [
{
"$ref": "#/definitions/timestamp"
}
]
},
"version": {
"allOf": [
{
"$ref": "#/definitions/semanticVersion"
}
]
},
"actor": {
"$id": "#actor",
"allOf": [
{
"$ref": "#/definitions/allOfAgentGroup"
}
]
},
"authority": {
"allOf": [
{
"$ref": "#/definitions/allOfAgentGroup"
}
]
},
"verb": {
"$id": "#verb",
"type": "object",
"properties": {
"id": {
"allOf": [
{
"$ref": "#/definitions/URI"
}
]
},
"display": {
"type": "object",
"allOf": [
{
"$ref": "#/definitions/lang5646"
}
]
}
}
},
"object": {
"$id": "#object",
"type": "object",
"additionalProperties": true,
"properties": {
"objectType": {
"type:": "string",
"enum": [
"Activity",
"Agent",
"Group",
"SubStatement",
"StatementRef"
]
}
}
}
},
"required": [
"actor",
"verb",
"object"
]
},
"attachment": {
"properties": {
"usageType": {
"allOf": [
{
"$ref": "#/definitions/URI"
}
]
},
"display": {
"allOf": [
{
"$ref": "#/definitions/lang5646"
}
]
},
"description": {
"allOf": [
{
"$ref": "#/definitions/lang5646"
}
]
},
"contentType": {
"type": "string",
"pattern": "\w+/[-+.\w]+;?(\w+.*=\w+;?)*"
},
"length": {
"type": "integer"
},
"sha2": {
"type": "string"
},
"fileUrl": {
"allOf": [
{
"$ref": "#/definitions/URI"
}
]
}
}
},
"semanticVersion": {
"type": [
"string"
],
"pattern": "^([0-9]+)\.([0-9]+)\\.([0-9]+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+)?$"
},
"Agent": {
"$id": "#Agent",
"allOf": [
{
"$ref": "#/definitions/IFI"
}
]
},
"AnonGroup": {
"$id": "#AnonGroup",
"maxProperties": 3,
"properties": {
"member": {
"type": "array",
"items": [
{
"allOf": [
{
"$ref": "#/definitions/allOfAgentGroup"
}
]
}
]
}
},
"dependencies": {
"objectType": [
"member"
]
},
"required": [
"member"
],
"not": {
"required": [
"mbox"
]
},
"not": {
"required": [
"mbox_sha1sum"
]
},
"not": {
"required": [
"openid"
]
},
"not": {
"required": [
"account"
]
}
},
"IdGroup": {
"$id": "#IdGroup",
"properties": {
"member": {
"type": "array",
"items": [
{
"allOf": [
{
"$ref": "#/definitions/allOfAgentGroup"
}
]
}
]
}
},
"allOf": [
{
"$ref": "#/definitions/IFI"
}
]
},
"allOfAgentGroup": {
"properties": {
"objectType": {
"type": "string",
"enum": [
"Agent",
"Group"
]
},
"name": {
"type": "string"
}
},
"oneOf": [
{
"if": {
"properties": {
"objectType": {
"const": "Agent"
}
}
},
"then": {
"allOf": [
{
"$ref": "#/definitions/Agent"
}
]
}
},
{
"if": {
"properties": {
"objectType": {
"const": "Group"
}
}
},
"then": {
"oneOf": [
{
"allOf": [
{
"$ref": "#/definitions/IdGroup"
}
]
},
{
"allOf": [
{
"$ref": "#/definitions/AnonGroup"
}
]
}
]
}
}
]
},
"IFI": {
"oneOf": [
{
"properties": {
"mbox": {
"allOf": [
{
"$ref": "#/definitions/mailto"
}
]
}
},
"required": [
"mbox"
]
},
{
"properties": {
"mbox_sha1sum": {
"type": "string",
"pattern": "\b[0-9a-f]{5,40}\b"
}
},
"required": [
"mbox_sha1sum"
]
},
{
"properties": {
"account": {
"properties": {
"homePage": {
"allOf": [
{
"$ref": "#/definitions/URI"
}
]
},
"name": {
"type": "string"
}
},
"required": [
"homePage",
"name"
]
}
},
"required": [
"account"
]
},
{
"properties": {
"openid": {
"allOf": [
{
"$ref": "#/definitions/URI"
}
]
}
},
"required": [
"openid"
]
}
]
},
"mailto": {
"type": "string",
"pattern": "(mailto:)(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])"
},
"timestamp": {
"type": "string",
"pattern": "^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(.[0-9]+)?(Z)?$"
},
"URI": {
"type": "string",
"pattern": "^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]"
},
"uuid": {
"type": "string",
"pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
},
"lang5646": {
"type": "object",
"patternProperties": {
"^((?:(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)|(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang))|((?:([A-Za-z]{2,3}(-(?:[A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-(?:[A-Za-z]{4}))?(-(?:[A-Za-z]{2}|[0-9]{3}))?(-(?:[A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-(?:[0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(?:x(-[A-Za-z0-9]{1,8})+))?)|(?:x(-[A-Za-z0-9]{1,8})+))$": {
"type": "string"
}
},
"additionalProperties": false
},
"lang5646string": {
"type": "string",
"pattern": "^((?:(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)|(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang))|((?:([A-Za-z]{2,3}(-(?:[A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-(?:[A-Za-z]{4}))?(-(?:[A-Za-z]{2}|[0-9]{3}))?(-(?:[A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-(?:[0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(?:x(-[A-Za-z0-9]{1,8})+))?)|(?:x(-[A-Za-z0-9]{1,8})+))$"
}
}
}
if/then/else
没有达到您的预期。
在您的模式定义中 allOfAgentGroup
有一个 oneOf
部分。
让我们单独看一下。
在您应该失败的示例数据中,让我们也单独使用 "actor" 对象。
架构:
{
"oneOf": [
{
"if": {
"properties": {
"objectType": {
"const": "Agent"
}
}
},
"then": false
}
},
false
]
}
实例数据:
{
"objectType": "Group",
"name": "Group Identified",
"mbox": "http://should.fail.com",
"member": [
{
"objectType": "Agent_shouldfail",
"name": "xAPI mbox",
"mbox": "mailto:shouldfail"
}
]
}
我们知道您希望 oneOf
数组中的第二部分项目无法通过验证。
出于调试和演示目的,我们假设它确实失败了,并将其更改为 false
(这是一个有效的 "JSON Schema",总是导致该分支上的验证失败)。
现在,鉴于上述架构和实例,您会期望 JSON 实例数据无法通过验证,对吗? 它没有,实际上验证通过了。
请记住,每个 JSON 架构关键字都会为您的验证要求添加约束。
让我们看看 if
和 then
实际上做了什么。
if
This validation outcome of this keyword's subschema has no direct effect on the overall validation result. Rather, it controls which of the "then" or "else" keywords are evaluated.
Instances that successfully validate against this keyword's subschema MUST also be valid against the subschema value of the "then" keyword, if present.
http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.6
好的,如果 if
的架构成功验证,则会应用 then
的架构。
实例数据未通过 if 条件验证,因此...未应用来自 then
的模式,最终得到的是有效的 "empty schema",这意味着没有验证oneOf/0
的限制条件。这导致您 oneOf
断言验证成功,因为 oneOf/0
通过而 oneOf/1
失败。
要解决此问题,您需要将 "then": false
添加到包含 if
和 then
的对象中,如果 [= =17=]条件不满足。