不能验证递归模式



我坚持为我的数据写一个正确的json模式。

条件:

  1. 目录是一个递归对象,具有可选属性"$meta"

  2. 文件可以是字符串或对象

    如果

    >文件

    是一个对象,它可以有一个可选的属性"$meta"与oneOf"$data"or"$stringData"不是。
  3. JSON数据:

{
"dir1": {
"dir1A": {},
"dir1B": {
"dir1B01": {
"dir1B0101": {},
"dir1B0102": {},
"dir1B0103": {},
"file1B.txt": {
"$meta": {},
"$data": "ooo",
"$stringData": "Netus et malesuada" // here should failed
}
}
}
},
"dir2": {
"file2.txt": "dolor sit amet" // here should OK but failed
},
"file1.txt": "Lorem Ipsum"
}

模式:

{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/dir.schema.json",
"type": "object",
"patternProperties": {
"^[^\/?%*:|"<>]+$": { // failed if name consist of invalid chars
"$ref": "#/$defs/directory"
}
},
"additionalProperties": false,
"$defs": {
"file": {
"oneOf": [
{
"type": "string"
},
{
"type": "object",
"properties": {
"$data": {
"type": "string",
"description": "base64 encoded data"
},
"$stringData": {
"type": "string"
}
},
"oneOf": [
{
"not": {
"anyOf": [
{
"required": [
"$data",
"$stringData"
]
}
]
}
},
{
"allOf": [
{
"not": {
"required": [
"$data"
]
}
},
{
"not": {
"required": [
"$stringData"
]
}
}
]
}
]
}
]
},
"directory": {
"anyOf": [
{
"$ref": "#/$defs/file"
},
{
"type": "object",
"properties": {
"$meta": {
"type": "object",
"additionalProperties": true,
"properties": {
"createdAt": {
"type": "string"
},
"size": {
"type": "integer"
},
}
}
},
"patternProperties": {
"^[^\/?%*:|"<>]+$": {
"$ref": "#"
}
}
}
]
}
}
}

Live Demo: https://www.jsonschemavalidator.net/s/HNFtNfRw

谢谢你的帮助

这里有一些问题,所以让我们一步一步地推导这个模式。

让我们从文件的对象表示开始。这是简单的部分。

{
"type": "object",
"properties": {
"$meta": { "$ref": "#/$defs/meta" },
"$data": { "type": "string" },
"$stringData": { "type": "string" }
}
}

然后我们需要添加断言,即$data$stringData中只有一个是必需的。oneOf关键字可以清楚地表达这一点。

"oneOf": [
{ "required": ["$data"] },
{ "required": ["$stringData"] }
]

只有一个模式可以验证为true。如果两个属性都存在,模式将失败。

接下来,让我们定义一个可以用字符串或对象表示的文件。

{
"anyOf": [
{ "type": "string" },
{ "$ref": "#/$defs/object-file" }
]
}

注意,在这种情况下,我们使用anyOf而不是oneOf。只要其中一个模式有效,anyOf就会通过验证。如果第一个通过了验证,则不需要检查第二个。如果使用oneOf,验证器需要检查两个模式,因为oneOf要求只有一个模式通过验证,其余模式失败。因为其中一个模式是字符串,另一个模式是对象,所以任何JSON数据都不可能同时是字符串和对象。因此,使用oneOf只会降低验证的效率。

这包括了文件,接下来我们来讨论目录。

{
"type": "object",
"properties": {
"$meta": { "$ref": "#/$defs/meta" }
},
"patternProperties": {
"^[^\/?%*:|"<>]+$": { "$ref": "#/$defs/file-or-directory" }
},
"additionalProperties": false
}

递归引用部分非常简单,但是我们有一个$meta属性的问题。$meta匹配patternProperties中的正则表达式,因此$meta将需要对#/$defs/meta#/$defs/file-or-directory进行验证,这不是您想要的。您需要的是类似additionalPatternProperties关键字的东西,但由于不存在,因此需要更改patternProperties正则表达式以排除$meta。像这样,"^[^\/?%*:|"<>]+(?<!\$meta)$".

现在剩下的就是一个模式来表示某个东西是一个文件或目录。

"anyOf": [
{ "$ref": "#/$defs/file" },
{ "$ref": "#/$defs/directory" }
]

同样,我们希望使用anyOf,但这比您第一眼可能注意到的要多一点。问题是匹配文件模式的内容也将匹配目录模式。因此,我们不能使用oneOf,文件模式需要放在第一位。如果目录模式是第一个,它将不正确地将文件验证为目录。当文件模式首先出现时,它首先检查它是否为文件,并且我们从目录模式中得到的假阳性变得无关紧要。

我将把它留给你把各个部分组合成一个完整的模式,但如果你有任何问题,请留下评论。

最新更新