具有两种根类型的 Json 架构验证失败

问题描述 投票:0回答:1

我有以下模式,我为有效负载选择两个子模式之一。

{
  "type": "object",
  "definitions": {
    "XXXTrades": {
      "type": "object",
      "title": "XXX Trades",
      "properties": {
        "instrumentTypes": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "minItems": 1
        }
      },
      "oneOf": [
        {
          "properties": {
            "books": {
              "type": "array",
              "items": {
                "type": "string"
              },
              "minItems": 1
            }
          },
          "required": [
            "books"
          ]
        },
        {
          "properties": {
            "tradeLegIds": {
              "type": "array",
              "items": {
                "type": "integer"
              },
              "minItems": 1
            }
          },
          "required": [
            "tradeLegIds"
          ]
        }
      ],
      "additionalProperties": false
    },
    "YYYYTrades": {
      "type": "object",
      "title": "YYYY Trades",
      "properties": {
        "sourceType": {
          "type": "string",
          "enum": [
            "YYYY"
          ]
        },
        "folioInstrumentFilter": {
          "type": "string"
        }
      },
      "anyOf": [
        {
          "type": "object",
          "title": "YYYY Folios",
          "properties": {
            "folios": {
              "type": "array",
              "items": {
                "type": "integer"
              },
              "minItems": 1
            }
          },
          "required": [
            "folios"
          ]
        },
        {
          "title": "YYYY Trade Ids",
          "type": "object",
          "properties": {
            "YYYYTradeIds": {
              "type": "array",
              "items": {
                "type": "integer"
              }
            },
            "minItems": 1
          },
          "required": [
            "YYYYTradeIds"
          ]
        }
      ],
      "required": [
        "sourceType"
      ],
      "allOf": [
        {
          "if": {
            "properties": {
              "folioInstrumentFilter": {
                "type": "string"
              }
            },
            "required": [
              "folioInstrumentFilter"
            ]
          },
          "then": {
            "required": [
              "folios"
            ]
          }
        }
      ],
      "additionalProperties": false
    }
  },
  "anyOf": [
    {
      "$ref": "#/definitions/YYYYTrades"
    },
    {
      "$ref": "#/definitions/XXXTrades"
    }
  ]
}

但是以下有效负载无法验证

{
  "YYYYTradeIds": [
    1
  ],
  "sourceType": "YYYY"
}

出现以下验证错误

{
  "errors": [
    {
      "instancePath": "",
      "schemaPath": "#/definitions/YYYYTrades/additionalProperties",
      "keyword": "additionalProperties",
      "params": {
        "additionalProperty": "YYYYTradeIds"
      },
      "message": "must NOT have additional properties",
      ...
...
..
    {
      "instancePath": "",
      "schemaPath": "#/definitions/XXXTrades/oneOf",
      "keyword": "oneOf",
      "params": {
        "passingSchemas": null
      },
      "message": "must match exactly one schema in oneOf",
      ...
...
  ]
}

验证器 AJV8 似乎无法确定在验证有效负载时使用哪个子模式。是否有任何机制可以强制解析器/验证器应用正确的子模式?

我尝试内联模式而不是使用定义,在 sourceType 上添加条件,但它不起作用。我已经在网上寻找解决方案几天了。

json jsonschema json-schema-validator
1个回答
0
投票

您的架构中有一个常见错误。

additionalProperties: false
和组合关键字(例如
anyOf
oneOf
)在早期版本的 JSON Schema 中不能很好地协同工作。 我假设您使用的是 Draft-07,这是默认的 AJV 8 实现。

要使此行为正常工作,您需要在根的子模式中定义关键字,使用空模式

{}
true
就足够了。

对于

if, then
,如果您已经在其他地方定义了
type
,通常应该使用模式约束而不仅仅是
type
。否则,对于您所拥有的用例来说,需要进行大量额外的处理和语法,即如果存在另一个属性,则强制执行特定属性。这可以通过
dependencies
来完成。

我对您的架构所做的更新,

  • 定义 JSON Schema 版本,这对于关键字行为很重要
  • 使用
    true
  • 在根定义子模式关键字
  • allOf>if>then
    约束替换为
    dependencies
  • minItems
    子模式
    中错位了 
    YYYYTradeIds
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "definitions": {
    "XXXTrades": {
      "type": "object",
      "title": "XXX Trades",
      "properties": {
        "instrumentTypes": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "minItems": 1
        },
        "books": true,
        "tradeLegIds": true
      },
      "oneOf": [
        {
          "properties": {
            "books": {
              "type": "array",
              "items": {
                "type": "string"
              },
              "minItems": 1
            }
          },
          "required": [
            "books"
          ]
        },
        {
          "properties": {
            "tradeLegIds": {
              "type": "array",
              "items": {
                "type": "integer"
              },
              "minItems": 1
            }
          },
          "required": [
            "tradeLegIds"
          ]
        }
      ],
      "additionalProperties": false
    },
    "YYYYTrades": {
      "type": "object",
      "title": "YYYY Trades",
      "properties": {
        "sourceType": {
          "type": "string",
          "enum": [
            "YYYY"
          ]
        },
        "folioInstrumentFilter": {
          "type": "string"
        },
        "folios": true,
        "YYYYTradeIds": true
      },
      "anyOf": [
        {
          "type": "object",
          "title": "YYYY Folios",
          "properties": {
            "folios": {
              "type": "array",
              "items": {
                "type": "integer"
              },
              "minItems": 1
            }
          },
          "required": [
            "folios"
          ]
        },
        {
          "title": "YYYY Trade Ids",
          "type": "object",
          "properties": {
            "YYYYTradeIds": {
              "type": "array",
              "items": {
                "type": "integer"
              },
              "minItems": 1
            }
          },
          "required": [
            "YYYYTradeIds"
          ]
        }
      ],
      "required": [
        "sourceType"
      ],
      "dependencies": {
        "folioInstrumentFilter": [
          "folios"
        ]
      },
      "additionalProperties": false
    }
  },
  "anyOf": [
    {
      "$ref": "#/definitions/YYYYTrades"
    },
    {
      "$ref": "#/definitions/XXXTrades"
    }
  ]
}

如果您有兴趣并愿意迁移到较新的 JSON 架构草案,则可以使用较新的关键字

additionalProperties
来避免架构根部的
unevaluatedProperties
问题和重复定义。 与 additionalProperties 不同,此关键字可以
查看
组合(allOf、oneOf、anyOf)关键字中定义的子模式。

2020-12 草案需要进行一些其他更改

  • $schema
    需要正确的版本
  • definitions
    更新为
    $defs
  • additionalProperties
    更新为
    unevaluatedProperties
  • 不再需要模式根部的重复关键字定义
  • Ajv 必须导入不同的模块来验证 Draft 2020-12。该模块包含在公共 Ajv 包中。
const Ajv = require('ajv/dist/2020');
const ajv = new Ajv({ strict: false });

let instance = // your json data instance
let schema = // your json schema

try {
    const validate = ajv.compile(schema)

    const valid = validate(instance)

    console.log(results = { valid: valid, error: validate.errors })
} catch ({ message }) {
    console.error(message)
}
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "$defs": {
    "XXXTrades": {
      "type": "object",
      "title": "XXX Trades",
      "properties": {
        "instrumentTypes": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "minItems": 1
        }
      },
      "oneOf": [
        {
          "properties": {
            "books": {
              "type": "array",
              "items": {
                "type": "string"
              },
              "minItems": 1
            }
          },
          "required": [
            "books"
          ]
        },
        {
          "properties": {
            "tradeLegIds": {
              "type": "array",
              "items": {
                "type": "integer"
              },
              "minItems": 1
            }
          },
          "required": [
            "tradeLegIds"
          ]
        }
      ],
      "unevaluatedProperties": false
    },
    "YYYYTrades": {
      "type": "object",
      "title": "YYYY Trades",
      "properties": {
        "sourceType": {
          "type": "string",
          "enum": [
            "YYYY"
          ]
        },
        "folioInstrumentFilter": {
          "type": "string"
        }
      },
      "anyOf": [
        {
          "type": "object",
          "title": "YYYY Folios",
          "properties": {
            "folios": {
              "type": "array",
              "items": {
                "type": "integer"
              },
              "minItems": 1
            }
          },
          "required": [
            "folios"
          ]
        },
        {
          "title": "YYYY Trade Ids",
          "type": "object",
          "properties": {
            "YYYYTradeIds": {
              "type": "array",
              "items": {
                "type": "integer"
              },
              "minItems": 1
            }
          },
          "required": [
            "YYYYTradeIds"
          ]
        }
      ],
      "required": [
        "sourceType"
      ],
      "dependentRequired": {
        "folioInstrumentFilter": [
          "folios"
        ]
      },
      "unevaluatedProperties": false
    }
  },
  "anyOf": [
    {
      "$ref": "#/$defs/YYYYTrades"
    },
    {
      "$ref": "#/$defs/XXXTrades"
    }
  ]
}
© www.soinside.com 2019 - 2024. All rights reserved.