Json Schema matching with 'anyOf' - json

I'd like to be able to manage a json array of 'objects' where each object has a type and properties, and there's a schema error if a mandatory property is missing from an object.
This is my attempt to do this (not including the array part) declaring two object types and saying the object in the json can be either of those types:
{
'definitions':
{
'typeone':
{
'type': 'object',
'properties':
{
'xtype': {'type':'string', 'const':'typeone'},
'num' : {'type':'number'}
},
'required':['xtype', 'num'],
'additionalProperties':false
},
'typetwo':
{
'type': 'object',
'properties':
{
'xtype': {'type':'string', 'const':'typetwo'},
'str' : {'type':'string'}
},
'required':['xtype', 'str'],
'additionalProperties':false
}
},
'anyOf':
[
{ '$ref': '#/definitions/typeone' },
{ '$ref': '#/definitions/typetwo' },
]
}
However, if I feed it json which fails because a mandatory property is missing from an object like this:
{
'xtype': 'typeone'
}
...it errors with JSON does not match any schemas from 'anyOf'. - I can see the reason is that it doesn't know to try to match on the xtype, instead it just considers xtype of 'typeone' invalid and looks to others.
Is there a better way to do anyOf which will hard-match based on one property value (like a 'switch') then give errors about missing other mandatory properties for that object type?

It gets a lot more verbose, but you can use if/then to switch validation based on the "xtype" property.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"allOf": [
{
"if": {
"type": "object",
"properties": {
"xtype": { "const": "typeone" }
},
"required": ["xtype"]
},
"then": { "$ref": "#/definitions/typeone" }
},
{
"if": {
"type": "object",
"properties": {
"xtype": { "const": "typetwo" }
},
"required": ["xtype"]
},
"then": { "$ref": "#/definitions/typetwo" }
}
],
"definitions": {
"typeone": {
"type": "object",
"properties": {
"xtype": {},
"num": { "type": "number" }
},
"required": ["num"],
"additionalProperties": false
},
"typetwo": {
"type": "object",
"properties": {
"xtype": {},
"str": { "type": "string" }
},
"required": ["str"],
"additionalProperties": false
}
}
}
With a small change to the model, you could use dependencies to get a much simpler cleaner schema. Instead of having an "xtytpe" property, you can have a property corresponding to the name of the type. For example, { "typeone": true }.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"dependencies": {
"typeone": {
"type": "object",
"properties": {
"typeone": {},
"num": { "type": "number" }
},
"required": ["num"],
"additionalProperties": false
},
"typetwo": {
"type": "object",
"properties": {
"typetwo": {},
"str": { "type": "string" }
},
"required": ["str"],
"additionalProperties": false
}
}
}

Related

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 for validating all child elements of a json Object node

I have a json document that i need to validate.
The json doc looks like this
"ParentElement": {
"child1": {
"property1": "hello",
"property2": {
}
}
},
"child2": {
"property1": "hello2",
"property2": {
}
}
}
i want all child elements of the ParentElement to have property1 and property2 as required elements.
required : ["property1", "property2"]
Note that i cannot use required inside the child elements because the name of the child elements differ and they are not fixed so the json schema wont have those names.
Tool i am using for validation:
com.github.fge:json-schema-validator:2.2.62
Validation is pretty straightforward
schema.validate(AboveJSONDOC);
I need to define a schema that allows all the child elements to have those two required properties.
You could define the validation schema for child object separately under definitions and refer that it to each child property that you want to validate.
{
"$schema": "http://json-schema.org/draft-07/schema",
"type": "object",
"properties": {
"ParentElement": {
"type": "object",
"properties": {
"child1": {
"$ref": "#/definitions/child"
},
"child2": {
"$ref": "#/definitions/child"
},
"childN": {
"$ref": "#/definitions/child"
}
}
}
},
"required": [
"ParentElement"
],
"definitions": {
"child": {
"type": "object",
"properties": {
"property1": {
"type": "string"
},
"property2": {
"type": "object"
}
},
"required": [
"property1",
"property2"
]
}
}
}
EDIT
When you don't know about the properties which may include in input JSON
you could use patternProperties to match properties using regular expressions.
Note that .* will accept every property. You could change it depending on your requirement.
{
"$schema": "http://json-schema.org/draft-07/schema",
"type": "object",
"properties": {
"ParentElement": {
"type": "object",
"patternProperties": {
".*": {
"$ref": "#/definitions/child"
},
"minProperties": 1
}
}
},
"required": [
"ParentElement"
],
"definitions": {
"child": {
"type": "object",
"properties": {
"property1": {
"type": "string"
},
"property2": {
"type": "object"
}
},
"required": [
"property1",
"property2"
]
}
}
}

Json schema for recursive structure not working

Here is my json schema. I want a recursive tree-like structure. But the response is passing even when I have invalid objects in the required array.
{
"$schema":"http://json-schema.org/draft-03/schema",
"properties":{
"Result":{
"type":"object",
"properties":{
"Children":{
"$ref":"#/definitions/Node"
}
},
"required":true
}
},
"required":true,
"type":"object",
"definitions":{
"Node":{
"type":"array",
"Items":{
"type":"object",
"properties":{
"Children":{
"$ref":"#/definitions/Node"
}
},
"required":true
}
}
}
}
To check that the JSON Schema validation will validate correctly , I deliberately put an invalid object inside the response -
{"Result":{"title":"title","Children":[{"invalidobject":"invalidobject"}]}}
But it is passing here - https://www.jsonschemavalidator.net/
What I actually want is Children to also have an array of Children and so on. So, only Children having objects with properties - title and Children should be allowed.
Its passing for this response too -
{"Result":{"title":"title","Children":[{"title":45}]}}
Try this schema. Note that this schema will allow children array to be empty even on the first level. After the first level, you have to allow children array to be empty otherwise, the schema will expect infinitely recursive data in order to pass validation.
{
"$schema": "http://json-schema.org/draft-03/schema",
"type": "object",
"properties": {
"Result": {
"$ref": "#/definitions/node",
"required":true,
}
},
"definitions": {
"node": {
"type": "object",
"properties": {
"title": {
"type": "string",
"required": true
},
"Children": {
"type": "array",
"required": true,
"items": {
"$ref": "#/definitions/node"
}
}
},
"additionalProperties": false
}
}
}
In case you do not want to allow children array to be empty at first level try defining the first level separately like this.
{
"$schema": "http://json-schema.org/draft-03/schema",
"type": "object",
"properties": {
"Result": {
"type": "object",
"required": true,
"properties": {
"title": {
"$ref": "#/definitions/title"
},
"Children": {
"minItems": 1,
"$ref": "#/definitions/Children"
}
},
"additionalProperties": false
}
},
"definitions": {
"title": {
"type": "string",
"required": true
},
"Children": {
"type": "array",
"required": true,
"items": {
"$ref": "#/definitions/node"
}
},
"node": {
"type": "object",
"properties": {
"title": {
"$ref": "#/definitions/title"
},
"Children": {
"$ref": "#/definitions/Children"
}
},
"additionalProperties": false
}
}
}
Here the validation results for sample input JSONs validated from https://www.jsonschemavalidator.net/
Input JSON:
{
"Result": {
"title": "title",
"Children": [
{
"invalidobject": "invalidobject"
}
]
}
}
Validation result:
Message:
Property 'invalidobject' has not been defined and the schema does not allow additional properties.
Schema path:
#/definitions/node/additionalProperties
Message:
Required properties are missing from object: title, Children.
Schema path:
#/definitions/node/required
Inpot JSON:
{
"Result": {
"title": "title",
"Children": [
{
"title": 45
}
]
}
}
Validation result:
Message:
Invalid type. Expected String but got Integer.
Schema path:
#/definitions/node/properties/title/type
Message:
Required properties are missing from object: Children.
Schema path:
#/definitions/node/required

why doesn't json schema validate definitions defined in required attribute

I'm trying to create a json schema that validates an object depending on its type. It picks the right definition, however, it doesn't validate the required attributes in the selected definition. Here is the json schema i am trying:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"literal": {
"type": "object",
"properties": {
"raw": { "type": "string" }
},
"required": ["raw"],
"additionalProperties": false
},
"identifier": {
"type": "object",
"properties": {
"name": { "type": "string" }
},
"required": ["name"],
"additionalProperties": false
}
},
"type": "object",
"oneOf": [
{
"type": "object",
"properties": {
"type": {
"enum": ["Literal"]
},
"content": { "$ref": "#/definitions/literal" }
}
},
{
"type": "object",
"properties": {
"type": {
"enum": ["Identifier"]
},
"content": { "$ref": "#/definitions/identifier" }
}
}
],
"required": ["type"]
};
the following schema is valid, even tho its missing the "raw" property:
{ "type" : "Literal" }
thanks
There is no keyword content in JSON Schema spec.
Once you have asserted "type":"object" in root schema, there is no need to do it again in subschema.
In order to combine object type enumerated value with associated extended definition, you need allOf keyword.
Also in definitions if you use "additionalProperties": false you have to list all properties of the object (see "type": {}). For previously defined/validated properties you can just use permissive schema: {} or true.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"literal": {
"properties": {
"type": {},
"raw": { "type": "string" }
},
"required": ["raw"],
"additionalProperties": false
},
"identifier": {
"properties": {
"type": {},
"name": { "type": "string" }
},
"required": ["name"],
"additionalProperties": false
}
},
"type": "object",
"oneOf": [
{
"allOf": [
{
"properties": {
"type": {
"enum": ["Literal"]
}
}
},
{"$ref": "#/definitions/literal"}
]
},
{
"allOf": [
{
"properties": {
"type": {
"enum": ["Identifier"]
}
}
},
{"$ref": "#/definitions/identifier" }
]
}
],
"required": ["type"]
}

JSON schema to validate that property name matches nested value

We are using JSON to store some configuration settings. For example:
{
"source1": {
"name": "source1",
"standalone": false
},
"source2": {
"name": "source2",
"standalone": true
},
"source3": {
"name": "source3",
"standalone": true
}
}
As you can see, the source names are variable and are repeated for convenience inside the object under a property name.
We're currently validating this using a JSON schema as follows:
{
"$schema": "http://json-schema.org/draft-06/schema#",
"type": "object",
"patternProperties": {
"^\\w[-\\w_]*$": { "$ref": "#/definitions/source" }
},
"additionalProperties": false,
"definitions": {
"source": {
"type": "object",
"properties": {
"name": { "type": "string" },
"standalone": { "type": "boolean" }
},
"required": ["name", "standalone"],
"additionalProperties": false
}
}
}
Is there a way to require that the property name matches the value using JSON schema? In other words, is there a way to make sure the following example fails to validate?
{
"a": {
"name": "b",
"standalone": false
}
}