admin管理员组

文章数量:1122846

I have the following schema where I am using a choice of one of two subschemas for the payload.

{
  "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"
    }
  ]
}

However the following payload fails to validate

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

with the following validation error

{
  "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",
      ...
...
  ]
}

It seems that the validator AJV8 is failing to determine which of the sub schemas to use when validating the payload. Is there any mechanism to force the parser/validator to apply the correct sub schema?

I tried inlining the schema instead of using definitions, adding conditions on sourceType but it didn't work.. I have been looking for solutions online for a couple of days.

I have the following schema where I am using a choice of one of two subschemas for the payload.

{
  "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"
    }
  ]
}

However the following payload fails to validate

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

with the following validation error

{
  "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",
      ...
...
  ]
}

It seems that the validator AJV8 is failing to determine which of the sub schemas to use when validating the payload. Is there any mechanism to force the parser/validator to apply the correct sub schema?

I tried inlining the schema instead of using definitions, adding conditions on sourceType but it didn't work.. I have been looking for solutions online for a couple of days.

Share Improve this question edited Nov 22, 2024 at 16:19 Jeremy Fiel 3,1792 gold badges11 silver badges25 bronze badges asked Nov 22, 2024 at 10:57 Mohammed Abu SharikhMohammed Abu Sharikh 1
Add a comment  | 

1 Answer 1

Reset to default 0

You have a common mistake in your schema. additionalProperties: false and composition keywords such as anyOf and oneOf don't play nice together in earlier versions of JSON Schema. I assume you are using draft-07, which is the default AJV 8 implementation.

To make this behavior work correctly, you need to define the keywords in the subschemas at the root, using an empty schema {} or true is sufficient.

With if, then, typically a schema constraint should be used more than just the type if you have already defined the type elsewhere. Otherwise, it's a lot of extra processing and syntax for the use case you have, which is to enforce a specific property if another property exists. This can be done with dependencies.

The updates I've made to your schema,

  • define JSON Schema version, this is important for the keyword behavior
  • define subschema keywords at the root with true
  • replace allOf>if>then constraint with dependencies
  • misplaced minItems in YYYYTradeIds subschema
{
  "$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"
    }
  ]
}

If you're interested and willing to move to a newer JSON Schema draft, you can avoid the additionalProperties issue and duplicate definitions at the root of the schema with the use of a newer keyword, unevaluatedProperties. This keyword can see into a subschema defined in a composition (allOf, oneOf, anyOf) keyword, unlike additionalProperties.

A few other changes required for Draft 2020-12

  • $schema requires the proper version
  • definitions is updated to $defs
  • additionalProperties is updated to unevaluatedProperties
  • dependencies is updated to dependentRequired
  • duplicate keyword definitions at the root of the schema are no longer required
  • Ajv must import a different module to validate Draft 2020-12. This module is included in the common Ajv package.
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"
    }
  ]
}

本文标签: jsonschemaJSON Schema anyOf and additionalProperties false failing validationStack Overflow