jsonschema - dynamic properties with static properties - json

I have a schema to validate a json.
For certain properties i need them to have values of certain types.
If "attr" property is "a" then "val" property should be "integer"
If "attr" property is "x" then "val" property should be "boolean"
If "attr" property is "b" then "val" property should be "string" with
format "ipv4"
and so on...
This, I can define with oneOff. For all other "attr" properties i need to them to be of a certain format, sort of like a catch all, with "val" property to be "string".
If "attr" matches pattern then "val" property should be "string".
can this be done.
This is the schema that i have at the moment.
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"name": {
"title": "name",
"type": "string"
},
"attribute": {
"title": "attributes",
"type": "object",
"$ref": "#/definitions/expr",
}
},
"definitions": {
"expr": {
"properties": {
"attr": {
"title": "attribute"
},
"val": {
"title": "val"
}
},
"required": ["val", "attr"],
"oneOf": [
{
"properties": {
"attr": {"enum": ["a","b"]},
"val": {"type": "integer"}
}
},
{
"properties": {
"attr": {"enum": ["x"]},
"val": {"type": "boolean"}
}
},
{
"properties": {
"attr": {"pattern": "^[-A-Za-z0-9_]*$", "maxLength": 255},
"val": {"type": "string"}
}
}
]
}
},
"additionalProperties": false,
"required": [
"name",
"attribute"
]
}
The problem is the properties for which i am trying to restrict the value type, also match the catchall format. so when i am expecting an integer value, it is passing with string value.
For Example:
the below json will pass the schema, based on the first item of oneOff
{
"name": "shouldpass",
"attribute": {
"attr": "a",
"val": 1
}
}
the below json will pass, based on the last item of oneOff.
{
"name": "shouldpass2",
"attribute": {
"attr": "h",
"val": "asd"
}
}
the below json should fail, based on the first item of oneOff, but it is also passing, because it is matching the last item of oneOff.
{
"name": "shouldfail",
"attribute": {
"attr": "a",
"val": "string"
}
}
how to acheive this?

You schema for attr in the last subschema could be:
{
"pattern": "^[-A-Za-z0-9_]*$",
"not": { "enum": ["a", "b", "x"] },
"maxLength": 255
}
Alternatively, instead of "oneOf" you can use "switch" keyword from the next JSON-schema version proposals: http://epoberezkin.github.io/ajv/keywords.html#switch-v5-proposal
It's implemented in Ajv (I am the author).

Related

Erroneous successful validation by JSON-schema

The fields in nodes depend on the value of entity. That is, if entity = "pd", then nodes has some fields, while entity = " top " - nodes has completely different fields, despite the fact that they are strictly required. For some reason, the JSON string is accepted by the valid schema, even if there are no fields defined in nodes as required. I already entire head broke, where can be mistake in the most scheme?
JSON-schema:
{
"definitions": {},
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "http://example.com/root.json",
"type": "object",
"title": "The Root Schema",
"required": [
"virtual"
],
"properties": {
"virtual": {
"$id": "#/properties/virtual",
"type": "array",
"title": "The Virtual Schema",
"items": {
"$id": "#/properties/virtual/items",
"type": "object",
"title": "The Items Schema",
"required": [
"type",
"path",
"entity",
"nodes"
],
"properties": {
"type": {
"$id": "#/properties/virtual/items/properties/type",
"type": "string",
"title": "The Type Schema",
"default": "",
"examples": [
"bus"
],
"pattern": "^(.*)$"
},
"path": {
"$id": "#/properties/virtual/items/properties/path",
"type": "string",
"title": "The Path Schema",
"default": "",
"examples": [
"VBUS2"
],
"pattern": "^(.*)$"
},
"entity": {
"$id": "#/properties/virtual/items/properties/entity",
"type": "string",
"title": "The Entity Schema",
"default": "",
"examples": [
"topaz"
],
"enum": ["pde", "topaz"],
"pattern": "^(.*)$"
},
"nodes": {
"$id": "#/properties/virtual/items/properties/nodes",
"type": "array",
"title": "The Nodes Schema",
"items": {
"$id": "#/properties/virtual/items/properties/nodes/items",
"type": "object",
"title": "The Items Schema"
}
}
}
}
}
},
"anyOf": [
{
"if": {
"properties": { "virtual": { "properties": { "entity": { "const": "topaz" } } } }
},
"then": {
"properties": {
"virtual": {
"properties": {
"nodes": {
"items": {
"required": [
"uid",
"utype",
"uaddress",
"unozzles"
],
"properties": {
"uid": {
"$id": "#/properties/virtual/items/properties/nodes/items/properties/uid",
"type": "integer",
"title": "The Uid Schema",
"default": 0,
"examples": [
1
]
},
"utype": {
"$id": "#/properties/virtual/items/properties/nodes/items/properties/utype",
"type": "string",
"title": "The Utype Schema",
"default": "",
"examples": [
"dispenser"
],
"pattern": "^(.*)$"
},
"uaddress": {
"$id": "#/properties/virtual/items/properties/nodes/items/properties/uaddress",
"type": "string",
"title": "The Uaddress Schema",
"default": "",
"examples": [
"false"
],
"pattern": "^(.*)$"
},
"unozzles": {
"$id": "#/properties/virtual/items/properties/nodes/items/properties/unozzles",
"type": "boolean",
"title": "The Unozzles Schema",
"default": false,
"examples": [
false
]
}
}
}
}
}
}
}
}
},
{
"if": {
"properties": { "virtual": { "properties": { "entity": { "const" : "pde" } } } }
},
"then": {
"properties": {
"virtual": {
"properties": {
"nodes": {
"items": {
"required": [
"id",
"type",
"address",
"nozzles"
],
"properties": {
"id": {
"$id": "#/properties/virtual/items/properties/nodes/items/properties/id",
"type": "string",
"title": "The Id Schema",
"default": "",
"examples": [
"vrt_1"
],
"pattern": "^(.*)$"
},
"type": {
"$id": "#/properties/virtual/items/properties/nodes/items/properties/type",
"type": "string",
"title": "The Type Schema",
"default": "",
"examples": [
"dispenser"
],
"pattern": "^(.*)$"
},
"address": {
"$id": "#/properties/virtual/items/properties/nodes/items/properties/address",
"type": "integer",
"title": "The Address Schema",
"default": 0,
"examples": [
1
]
},
"nozzles": {
"$id": "#/properties/virtual/items/properties/nodes/items/properties/nozzles",
"type": "array",
"title": "The Nozzles Schema",
"items": {
"$id": "#/properties/virtual/items/properties/nodes/items/properties/nozzles/items",
"type": "integer",
"title": "The Items Schema",
"default": 0,
"examples": [
1,
2,
3
]
}
}
}
}
}
}
}
}
}
}
]
}
This JSON is valid:
{
"virtual": [
{
"type": "bus",
"path": "VUS1",
"entity": "pde",
"nodes": [
{
"id": "vrt_1",
"type": "string",
"address": 1,
"nozzles": [1, 2, 3]
},
{
"id": "vrt_2",
"type": "string",
"address": 2,
"nozzles": [1, 2, 3]
}
]
},
{
"type": "bus",
"path": "VUS2",
"entity": "topaz",
"nodes": [
{
"uid": 1,
"utype": "string",
"uaddress": "false",
"unozzles": false
},
{
"uid": "vrt_1",
"utype": "string",
"uaddress": "false",
"unozzles": false
}
]
}
]
}
And this JSON should not be applied, but is considered valid:
{
"virtual": [
{
"type": "bus",
"path": "VUS1",
"entity": "pde",
"nodes": [
{
"id_not_valid": "failure",
"type": 1,
"address": false,
"nozzles": [1, 2, 3]
},
{
"id": "vrt_2",
"type": "string",
"address": false,
"nozzles": [1, 2, 3]
}
]
},
{
"type": "bus",
"path": "VUS2",
"entity": "topaz",
"nodes": [
{
"uid_not_valid": "failure",
"utype": 1,
"uaddress": "false",
"unozzles": false
}
]
}
]
}
In theory, the second JSON should not be validated. For several reasons:
For entity= "pd", the required fields are "id", "type"," address "and"nozzles". In the second line of JSON instead the field "id" is replaced by the field "id_not_valid" - > the obligatory field " id " is absent and validation has to end in failure. The same for entity="top" - "the uid" is replaced by "id_not_valid"
For entity= "pd", the address field is of type token, in the second JSON line it is set to false, which corresponds to the type "boolean", but validation still takes place (the same if you assign an array or string value to address). For entity="top" type, the type is string, but the integer value 1 assigned to it is also assumed by the validator to be the correct string.
But the online validators on the links below say that everything is OK and both JSON conform to the scheme.
The first site
Second site
The third website
So I believe there is an error in the scheme.
The scheme itself was made by this example Example of JSON schema compilation
Any comments and tips on fixing JSON-schema, please
The schema is malformed.
(I'm ignoring the fact that the schema states entity should be "pde" or "topaz", but the instances have "pd" and "top". I assume this is a typo.)
Inside the anyOf, you have two items, each with an if conditional keyword. The schema presented by this keyword is
{
"properties": {
"virtual": {
"properties": {
"entity": {
"const": "topaz"
}
}
}
}
}
This is saying that if virtual has an entity property, then it should be "topaz". But the way that properties works is that it only fails validation if the instance is an object. However in #/properties, you declare that virtual should be an an array of objects where each item contains an entity property.
Since virtual is an array in your instance, none of the if condition keywords in the anyOf pass, so they defer to the else keywords for those subschemas, which don't exist (so the pass by default). This results in both subschemas for the anyOf passing.
I think what you're trying to do is validate each of the items inside the array based on the value of the entity property for that item. This means that you could have both a pde item and a topaz item in the array.
To do this you need to isolate where the variance is. In your case, it's the item level inside the virtual array. This is where you need to put your anyOf.
So you'll want to add your anyOf to #/properties/virtual/items. This is the only point in the schema where an if/then construct can key off of the entity property and enforce the nodes property.
Edit Things I would change
Remove all of the internal $id declarations. They only reiterate the location in the document and provide no additional functionality.
Remove the type and pattern declarations from entity. enum is sufficient here because it declares that the values must be one of the items in the array. Since these are both strings and match the given pattern, those keywords are redundant.
Move the anyOf alongside the properties keyword inside virtual and change it to a oneOf. This is the most specific location where you can access both the entities property and the nodes property. Changing it to a oneOf ensures that exactly one can be true.
Drop the if/then construct and just include the constant value in the then portion.
In the end, it would be structured something like this:
{
... ,
"properties": {
"virtual": {
"type": "array",
"title": "The Virtual Schema",
"items": {
"type": "object",
"title": "The Items Schema",
"required": [ "type", "path", "entity", "nodes" ],
"properties": {
"type": { ... },
"path": { ... },
"entity": {
"title": "The Entity Schema",
"default": "",
"examples": [
"topaz"
],
"enum": ["pde", "topaz"]
}
},
"oneOf": [
{
"properties": {
"entity": {"const": "topaz"},
"nodes": { ... }
}
},
{
"properties": {
"entity": {"const": "pde"},
"nodes": { ... }
}
}
]
}
}
}
}
Here, we're declaring that the items within the virtual array must be objects requiring 4 properties: type, path, entity, and nodes. We explicitly define type, path, entity using the properties keyword. But we conditionally define the nodes property using the oneOf and specifying a constant value for the entity property in each case.

JSONSchema Validation array items

I receive the following items in a JSON Array
{locations: [{locId: "1", locName: "ST1"}, {locId: "2", locName: "ST2"}, {locId: "3", locName: "ST3"}]}
My requirement is that inside locations the value for locName has to be one of ST1, ST2, or ST3 (I don't care about locId). How do I enforce this in the JSONSchema. I saw you can use array but how do I specify that an object\item of the array should have one of the pre-defined values for a particular property.
You can use the enum keyword to restrict the allowed values for a given property:
"locName": {
"type": "string",
"enum": ["ST1", "ST2", "ST3"]
}
Complete schema:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"locations": {
"type": "array",
"items": [
{
"type": "object",
"properties": {
"locId": {
"type": "string"
},
"locName": {
"type": "string",
"enum": ["ST1", "ST2", "ST3"]
}
},
"required": [
"locId",
"locName"
]
},
]
}
},
"required": [
"locations"
]
}

How to describe this validation requirement in jsonschema?

I want to validate my API json response like this:
{
"code": 0,
"results": [
{"type":1, "abc": 123},
{"type":2, "def": 456}
]
}
I want to validate the objects within results to have a "abc" field when its type is 1, and "def" field when its type is 2. The results may contain arbitrary number of type1 and type2 objects.
Can I specify this in jsonschema? Or must I use a generic validator for elements in results and do validation myself?
You can do this using the anyOf keyword.
An instance validates successfully against this keyword if it validates successfully against at least one schema defined by this keyword's value.
http://json-schema.org/latest/json-schema-validation.html#anchor85
You need to define both types of items and then use anyOf To describe the array items for "results".
{
"type": "object",
"properties": {
"code": { "type": "integer" },
"results": {
"type": "array",
"items": { "$ref": "#/definitions/resultItems" }
}
},
"definitions": {
"resultItems": {
"type": "object",
"anyOf": [
{ "$ref": "#/definitions/type1" },
{ "$ref": "#/definitions/type2" }
]
},
"type1": {
"properties": {
"type": { "enum": [1] },
"abc": { "type": "integer" }
},
"required": ["abc"]
},
"type2": {
"properties": {
"type": { "enum": [2] },
"def": { "type": "integer" }
},
"required": ["def"]
}
}
}

What is the difference between "anyof" and "oneof" in z schema?

Its looking like both works fine with my input validation code. Then what is the exact difference?
Schema with oneof
[{
"id": "MyAction",
"oneOf": [{ "$ref": "A1" },
{ "$ref": "A2" }]
},
{
"id": "A1",
"properties": {
"class1": { "type": "string"},
"class2": { "type": "string"}
}
},
{
"id": "A2",
"properties": {
"class2": { "type": "string"},
"class3": { "type": "string"}
}
}
]
Schema with anyof
[{
"id": "MyAction",
"anyOf": [{ "$ref": "A1" },
{ "$ref": "A2" }]
},
{
"id": "A1",
"properties": {
"class1": { "type": "string"},
"class2": { "type": "string"}
}
},
{
"id": "A2",
"properties": {
"class2": { "type": "string"},
"class3": { "type": "string"}
}
}
]
If you look at the JSON Schema documentation, it says:
anyOf:
...
An instance validates successfully against this keyword if it
validates successfully against at least one schema defined by this
keyword's value. Note that when annotations are being collected, all
subschemas MUST be examined so that annotations are collected from
each subschema that validates successfully.
oneOf:
...
An instance validates successfully against this keyword if it
validates successfully against exactly one schema defined by this
keyword's value.
Note my emphasis in the above. anyOf means the item must validate against at least one (but possibly more than one) of the schemas. oneOf means it must validate against only one of the schemas.
I am late in the quest but as per my understanding the usage of this keyword depends on the type of the object/parent itself. for example if you are trying to define the type for a single property of the object or the element of an array. take the below example :
{
"title": "Sample JSON Schema",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"definitions": {
"propObjectType1" : {
"name": "string",
"age": "number"
},
"propObjectType2" : {
"name": "string",
"dob": {
"type": "string",
"pattern": "\\d\\d\/\\d\\d\/\\d\\d\\d\\d"
}
}
},
"properties": {
"prop1": {
"type": "string",
"maxLength": 64
},
"prop2": {
"anyOf": [
{
"$ref": "#/definitions/propObjectType1"
},
{
"$ref": "#/definitions/propObjectType2"
}
]
},
"prop3": {
"oneOf": [
{
"$ref": "#/definitions/propObjectType1"
},
{
"$ref": "#/definitions/propObjectType2"
}
]
},
"prop4Array": {
"type": "array",
"items": {
"oneOf": [
{
"$ref": "#/definitions/propObjectType1"
},
{
"$ref": "#/definitions/propObjectType2"
}
]
}
},
"prop5Array": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/propObjectType1"
},
{
"$ref": "#/definitions/propObjectType2"
}
]
}
}
}
}
So in the above definition the prop2 and prop3 are the same (you can use interchangeably anyOf or oneOf) and you can define what ever you are comfortable with. but, in case of array:
when you use anyOf for the items type, the elements can be of any type out of those and the array can contain the mixed items. Means you can have one item of type 1 and another item of type 2.
when you use oneOf for the items type, the elements can be of any type out of those and the array can contain only one type of items. Means all the items must be of the same type (either type 1 or type 2).

How to define JSON schema for object that holds Properties object?

I need to create a JSON schema for object that will include java Properties object as one of its properties.
The nested Properties object will be simply list of key=value. Both key and value are of type string.
I failed to find any docs that describe how to define the schema that includes 2 new types.
shall it be something like:
{
"type": "object",
"name": "MyObj",
"properties": {
"prop1": {
"type": "string",
"description": "prop1",
"required": true
},
"props": {
"type": "array",
"items": {
"type": "object"
"properties": {
"key": {
"type": "string",
"description": "key",
"required": true
},
"value": {
"type": "string",
"description": "the value",
"required": true
}
}
"description": "the value",
"required": true
}
}
}
}
The schema you have written (assuming the commas are fixed) describes data of the form:
{
"prop1": "Some string property goes here",
"props": [
{"key": "foo", "value": "bar"},
{"key": "foo2", "value": "bar2"},
...
]
}
If this is what you wanted, then you are already finished.
However, I do wonder why you are using key/value pairs in an array, when you could use a JSON object with string keys instead. Using the additionalProperties keyword, you could have a schema:
{
"type": "object",
"name": "MyObj",
"properties": {
"prop1": {
"type": "string",
"description": "prop1"
},
"props": {
"type": "object",
"additionalProperties": {
"type": "string",
"description": "string values"
}
}
}
}
This describes a data format like:
{
"prop1": "Some string property goes here",
"props": {
"foo": "bar",
"foo2": "bar2"
}
}
At W3 schools (JSON Syntax) you can read how the array should be defined.
There is no schema like the xsd for xml, however i've found an approach on json-schema.org. If you are able to, i'll advice to youse google-GSON library for JSON. You could Store key Value as "id" : "value" and build only one object, containing all requieed pairs:
{ "lang" : "EN" , "color" : "red" }
Your posted model is incorect, you can check it on jsonlint.com
Here is a working version, i'm not sure if the modell is as expected.
{
"type": "object",
"name": "MyObj",
"properties": [
{
"prop1": {
"type": "string",
"description": "prop1",
"required": true
},
"props": {
"type": "array",
"items": {
"type": "object",
"properties": {
"key": {
"type": "string",
"description": "key",
"required": true
},
"value": {
"type": "string",
"description": "the value",
"required": true
}
},
"description": "the value",
"required": true
}
}
}
]
}