JSON Schema OneOf Cascading - json

What I am asking myself is if I can cascade multiple 'oneOf' or maybe there is a better way to make my Cases Valid.
I am Trying to validate the following:
Use definition of ObjectA or ObjectB as Single Objects or an Array of a them
Case 1:
Using only definition of ObjectA
{
"X": "test"
}
Case 2:
Using only definition of ObjectB
{
"Y": "test"
}
Case 3:
Using definition of ObjectA or ObjectB in an Array
[
{
"X": "test"
},
{
"Y": "test"
}
]
Case 4:
Using definition of ObjectA twice in an Array
[
{
"X": "test"
},
{
"X": "test"
}
]
Schema:
I tryed using this schema, the IntelliSense of MonacoEditor is working well but I still get the Error/Warning: "Matches multiple schemas when only one must validate."
{
"definitions": {
"objectA": {
"type": "object",
"properties": {
"X": {
type: "string"
}
}
},
"objectB": {
"type": "object",
"properties": {
"Y": {
type: "string"
}
}
}
},
"oneOf":
[
{
"oneOf":
[
{
"$ref": "#definitions/objectA"
},
{
"$ref": "#definitions/objectB"
}
]
},
{
"type": "array",
"items":
{
"oneOf":
[
{
"$ref": "#definitions/objectA"
},
{
"$ref": "#definitions/objectB"
}
]
}
}
]
}
Error/Warning:
"Matches multiple schemas when only one must validate."

The problem is that your X property in objectA and Y property en objectB are not required, so an empty object, that is { }, validates against both.
Also, if you want an array with objectA and objectY to be valid, you need to use anyOf instead of oneOf.
{
"definitions": {
"objectA": {
"type": "object",
"properties": {
"X": {
"type": "string"
}
},
"required": ["X"]
},
"objectB": {
"type": "object",
"properties": {
"Y": {
"type": "string"
}
},
"required": ["Y"]
}
},
"oneOf":
[
{"$ref": "#/definitions/objectA"},
{"$ref": "#/definitions/objectB"},
{
"type": "array",
"minItems": 1,
"items":
{
"anyOf":
[
{"$ref": "#/definitions/objectA"},
{"$ref": "#/definitions/objectB"}
]
}
}
]
}
I added the minItems if you don't want an empty array to validate.

Related

How to merge two json schema without additional properties

I have two types defined by a JSON Schema:
Type A
{
"type": "object",
"properties": {
"a": { "type": "string" }
}
}
Type X
{
"type": "object",
"properties": {
"x": { "type": "string" }
}
}
I want to create a schema combining A and X, but without additional properties. (only properties 'a' and/or 'x' should be present). I tried to use "allOf" but I can't add a restriction about additional properties. If I add "additionalProperties" in A or X, it doesn't work.
How should I process ? Of course, I don't want to repeat A into X
What you're looking for is the unevaluatedProperties feature available in jsonschema 2019-09 upwards. In contrast to additionalProperties, which only concerns the locally defined properties, unevaluatedProperties works across sub-schemas (but is more expensive to evaluate).
The schema
{
"anyOf": [
{
"type": "object",
"properties": {
"a": {
"type": "string"
}
}
},
{
"type": "object",
"properties": {
"x": {
"type": "string"
}
}
}
],
"unevaluatedProperties": false
}
Allows
{
"a": "foo",
"x": "bar"
}
But disallows
{
"a": "foo",
"x": "bar",
"t": "baz"
}
If you're stuck with an earlier jsonschema version, you have to manually define the properties of the sub-schemas in the parent like this:
{
"anyOf": [
{
"type": "object",
"properties": {
"a": {
"type": "string"
}
}
},
{
"type": "object",
"properties": {
"x": {
"type": "string"
}
}
}
],
"properties": {
"a": true,
"x": true
},
"additionalProperties": false
}

Json Schema Validator Array Object property is based on enum value

I have to validate json Array object based on property enum value.
{
"college":[
{
"staffDetails": {
"etype": "hod",
"workHandling": "department"
}
},
{
"staffDetails": {
"etype": "professor",
"workHandling": "CSE"
"DataStructure": true
"Maths": false
}
}
]
}
My json schema
{
"type": "object",
"properties": {
"college": {
"type": "array",
"minItems": 2
"items":{
"$ref": "#definitions/deatils"
}
}
},
"required": ["college"],
"definition": {
"details": {
"type": "object",
"properties": {
"etype": { "enum": ["hod","professor","assistant professor"], "type":"string"},
"workHandling": {"type": "string"}
"DataStructure": {"type": "boolean"}
"Maths": {"type": "boolean"}
},
"oneOf": [
{
"properties" {
"etype": { "const": "hod"}
},
"required": ["workHandling"]
},
{
"properties" {
"etype": { "const": "professor"}
},
"required": ["workHandling", "DataStructure", "Maths"]
}
]
}
}
}
Based on "etype" inside the array object, that Array object property should marked are required.
but the schema definition is not working as expected, its expecting all ["workHandling", "DataStructure", "Maths"] in array object index 0 and 1 and so on even etype is hod.
Any suggestion, where iam missing of any other better solution...

Json Schema for Aspect Oriented Json files

So my json structure is aspect oriented, meaning that the json is structure in a way that each data is represented by a key and that key will define the structure of its content.
for example:
[
{
"nv": [{ "ln": 123 }]
},
{
"metadata": [{ "name": "nodes" }, { "name": "edges" }]
},
{
"nodes": [{ "#id": 1 }, { "#id": 2 }]
},
{
"edges": [
{ "#id": 1, "nodeId": 1 },
{ "#id": 2, "nodeId": 2 }
]
},
{
"status": [{ "success": true }]
}
]
As shown 3 objects (nv, metadata, status) and based on the name inside of the metadata there will be objects inside the json file.
I tried something like this:
{
"type": "array",
"items": [
{
"type": "object",
"properties": {
"nv": { "type": "array", "items": { "$ref": "#definitions/nv" } }
},
"required": ["nv"]
},
{
"type": "object",
"properties": {
"metaData": {
"type": "array",
"items": { "$ref": "#definitions/metadata" }
}
},
"required": ["metaData"]
},
{
"anyOf": [
{
"type": "object",
"properties": {
"nodes": {
"type": "array",
"items": { "$ref": "#definitions/nodes" }
}
}
},
{
"type": "object",
"properties": {
"edges": {
"type": "array",
"items": { "$ref": "#definitions/edges" }
}
}
},
{
"type": "object",
"properties": {
"edgeAttribute": {
"type": "array",
"items": { "$ref": "#definitions/edgeAttribute" }
}
}
},
{
"type": "object",
"properties": {
"nodeAttribute": {
"type": "array",
"items": { "$ref": "#definitions/nodeAttribute" }
}
}
}
]
},
{
"type": "object",
"properties": {
"status": {
"type": "array",
"items": { "$ref": "#definitions/status" }
}
},
"required": ["status"]
}
],
"definitions": {
"status": {
"type": "object",
"properties": {
"success": { "type": "boolean" }
}
"etc..."
}
}
}
but then if I define an empty array it will be accepted, also it is being accepted if the array only contains one of the 3 required objects.
So is there a way to validate against something like the example using json-schemas?
The real scenario may have more than just 2 objects inside of the metadata that's why I did not use if -> then -> else conditions. if the solution is by using them then please let me know.
The structure of the data makes this a rough one, but there are a few patterns you can use to get the behavior you want. Let's take them one at a time.
Declare an array that can contain any of a number of objects
Generally people use oneOf for this, but I don't recommend that because it can have poor performance and incomprehensible error messages. Usually that means if/then, but in this case you can get good results by defining your items as a single object that only allows one property at a time in each object.
{
"items": {
"type": "object",
"properties": {
"nv": { "$ref": "#/definitions/nv" },
"metadata": { "$ref": "#/definitions/metadata" },
"status": { "$ref": "#/definitions/status" },
"nodes": { "$ref": "#/definitions/nodes" },
"edges": { "$ref": "#/definitions/edges" }
},
"minProperties": 1,
"maxProperties": 1
}
}
Edit: Previously, I recommended dependencies, but then realized that this is better.
Assert that the array contains a required object
To do this, you need to assert that the array contains an object that has a required property.
{ "contains": { "type": "object", "required": ["nv"] } }
You'll have to combine this pattern in allOf to express additional required items.
{
"allOf": [
{ "contains": { "type": "object", "required": ["nv"] } },
{ "contains": { "type": "object", "required": ["metadata"] } },
{ "contains": { "type": "object", "required": ["status"] } }
]
}
Conditionally assert that the array contains a required object
The tricky part here is getting all the nested contains and properties in the if to be able to assert that the "name" is a certain value. The then just uses the same pattern we used above to assert than an object is required in the array.
{
"if": {
"type": "array",
"contains": {
"type": "object",
"properties": {
"metadata": {
"type": "array",
"contains": {
"type": "object",
"properties": {
"name": { "const": "nodes" }
},
"required": ["name"]
}
}
},
"required": ["metadata"]
}
},
"then": { "contains": { "type": "object", "required": ["nodes"] } }
}
The above example shows the "nodes" object being conditionally required. You'll need to repeat this pattern for the "edges" object and combine them with allOf. I suggest making use of definitions to help make this readable.
{
"allOf": [
{ "$ref": "#/definitions/if-metadata-has-nodes-then-require-nodes-object" },
{ "$ref": "#/definitions/if-metadata-has-edges-then-require-edges-object" }
]
}
I would suggest moving each of your "types" into a $defs to be referenced.
{
"$defs": {
"nvObj": {
"type": "object",
"properties": {
"nv": { "type": "array", "items": { "$ref": "#/$defs/nv" } }
},
"required": ["nv"]
},
... // other defitions
}
}
(I've updated the $ref to use $defs instead of definitions as this is the new keyword since draft 7.)
Then you can put many references into a oneOf.
{
"$defs": {
... // from above
},
"type": "array",
"items": {
"oneOf": [
{ "$ref": "#/$defs/nvObj" },
... // all of the other object definitions
]
}
}
You're right to avoid if/then/else for this case. oneOf is the best bet here.

Json Schema Polymorphism Validate with anyOf

I've got a Pet object that could be either a dog or a cat
Depending on what noise they make I'd like to then be able to validate other fields.
schema:
{
"$id": "http://example.com",
"definitions": {
"pet": {
"type": "object",
"properties": {
"noise": {
"enum": [
"bark",
"meow"
]
}
}
},
"dog": {
"$ref": "#/definitions/pet",
"properties": {
"noise": {
"const": "bark"
},
"tail": {
"enum": [
"short",
"long"
]
}
}
},
"cat": {
"$ref": "#/definitions/pet",
"properties": {
"noise": {
"const": "meow"
},
"tail": {
"enum": [
"wavy",
"slinky"
]
}
}
}
},
"type": "object",
"properties": {
"pets": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/dog",
"$ref": "#/definitions/cat"
}
]
}
}
}
}
This works when running the following json through:
{"pets":[{"noise":"meow","tail":"wavy"}]}
but not when running:
{"pets":[{"noise":"bark","tail":"long"}]}
[$.pets[0].tail: does not have a value in the enumeration [wavy, slinky], $.pets[0].noise: must be a constant value meow]
or
{"pets":[{"noise":"bark","tail":"long"},{"noise":"meow","tail":"wavy"}]}
[$.pets[0].tail: does not have a value in the enumeration [wavy, slinky], $.pets[0].noise: must be a constant value meow]
I can get this working by using if/else in the json schema, but requires another type to avoid a circular dependency:
"petWithConstraints": {
"$ref":"#/definitions/pet",
"allOf": [
{
"if": {
"properties": {
"noise": {
"const": "bark"
}
}
},
"then": {
"$ref": "#/definitions/dog"
}
},
{
"if": {
"properties": {
"noise": {
"const": "meow"
}
}
},
"then": {
"$ref": "#/definitions/cat"
}
}
]
}
}
This means for every new definition it also requires another if statement.
Is there a better method of doing this? (without the extra definition/if statement)
For those that come across this, this was a syntactical error.
Each ref should have been in it's own code block.
The corrected part of the schema looks like the following:
"properties": {
"pets": {
"type": "array",
"items": {
"anyOf": [
{ // Notice each $ref is encapsulated in it's own block
"$ref": "#/definitions/cat"
},
{
"$ref": "#/definitions/dog"
}
]
}
}
}
Running the following json through gave expected results
{"pets":[{"noise":"bark","tail":"long"},{"noise":"meow","tail":"wavy"}]}
[]
{"pets":[{"noise":"bark","tail":"long"},{"noise":"meow","tail":"wavy"},{"noise":"meow","tail":"slinky"},{"noise":"bark","tail":"short"}]}
[]
{"pets":[{"noise":"bark","tail":"long"},{"noise":"meow","tail":"wavy"},{"noise":"meow","tail":"slinky"},{"noise":"bark","tail":"short"},{"noise":"meow","tail":"short"}]}
[$.pets[4]: should be valid to one and only one of the schemas ]

Require a specific item in an array

I have the following json object:
{
"my_items": [
{ "a": "primary", n: 1 },
{ "b": "secondary", n: 2 },
{ "b": "secondary", n: 3 }
]
}
All items in the my_items list are expected to be unique. Now, I need to validate the entire json object with the following rule:
it may contain zero or multiple items with "type": "secondary", but it absolutely must contain one and only one item with "type": "primary".
How can this be expressed using latest json-schema?
I'm come up with the following:
var schema = {
"definitions": {
"primary_item": {
"type": "object",
"properties": {
"a": {
"type":"string",
"enum":["primary"]
}
}
},
"secondary_item": {
"type": "object",
"properties": {
"b": {
"type": "string",
"enum":["secondary"]
}
}
}
},
"type": "object",
"properties": {
"my_items": {
"type": "array",
"minItems": 1,
"contains": {"$ref": "#/definitions/primary_item"},
"items": {
"anyOf": [
{"$ref": "#/definitions/primary_item"},
{"$ref": "#/definitions/secondary_item"}
],
"additionalProperties": false
}
}
},
"additionalProperties": false
};
var validate = ajv.compile(schema);
test({
"my_items": [
{"a": "primary"},
{"b": "secondary"},
{"b": "secondary"}
]
});
But the tests are failing, with the following errors:
Invalid: data.my_items[0] should NOT have additional properties, data.my_items[1] should NOT have additional properties, data.my_items[2] should NOT have additional properties
There is no way in JSON Schema to assert that an array contains one and only one of something. You can assert there is at least one, but that's the best you can do. The closest you can get is to require that the primary_item is the first element in the array.
{
"type": "object",
"properties": {
"my_items": {
"type": "array",
"items": [
{"$ref": "#/definitions/primary_item"}
],
"additionalItems": {"$ref": "#/definitions/secondary_item"}
}
},
"additionalProperties": false,
"definitions": {
"primary_item": {
"type": "object",
"properties": {
"a": { "enum":["primary"] }
},
"additionalProperties": false
},
"secondary_item": {
"type": "object",
"properties": {
"b": { "enum":["secondary"] }
},
"additionalProperties": false
}
}
}
EDIT
Responding to the comment
I wonder if one and only one could be expressed using two concepts: at least one + unique.
Yes. If your array items are unique you can do the following.
{
"type": "object",
"properties": {
"my_items": {
"type": "array",
"items": { "$ref": "#/definitions/my_item" },
"allOf": [{"$ref": "#/definitions/contains_primary_item"}],
"uniqueItems": true
}
},
"additionalProperties": false,
"definitions": {
"my_item": {
"type": "object",
"properties": {
"a": { "type": "string" }
},
"additionalProperties": false
},
"primary_item": {
"type": "object",
"properties": {
"a": { "enum":["primary"] }
},
"additionalProperties": false
},
"contains_primary_item": {
"not": {
"items": {
"not": { "$ref": "#/definitions/primary_item" }
}
}
}
}
}