JSON schema defining single use of elements from a set - json

I using draft-04 to define JSON schema.
My original problem was something like i wanted to define schema for below structure
{
"config1" : [
{ <category-1> : {some Object which i know how to define schema} },
{ <category-2> : {some Object which i know how to define schema} }
],
"config2" : {some other structure}
}
here category-n is a category defined in n-element set containing categories.
I know a schema defines a static data structure hence we can't enumerate on keys (correct?) so as a work around, i need to have config1 as follows
"config1" : [
{ "categoryName" : "category-1"
"categoryPayload" : {some Object which i know how to define schema} },
{ "categoryName" : "category-2"
"categoryPayload" : {some Object which i know how to define schema} }
]
for each category definition, i know i can use like "enum": ["category-1", "category-1"] but the problem is, in the first json i was getting benefit of json recommendation of not using duplicate keys. Each category is unique and i dont want someone to use different categoryPayload for the same category. How can i restrict array elements of config-1 to just have 1 element per category (no duplicate category elements)
Edit-1
Taking example of my derived approach (using category as value instead of keys), suppose category-1, category-2 and category-3 are allowed categories. hence following json should pass the validation
{
"config-1": [
{
"categoryName": "category-1", //line-1
"categoryPayload": {
"key1": "value1",
"key2": "value2"
}
},
{
"categoryName": "category-2", //line-2
"categoryPayload": {
"key1": "value1",
"key3": "value3"
}
}
],
"config-2": "something"
}
If a replace line-1 with category-2 (both line-1 and 2 having same values), this should fail the validation. And obviously if i replace category-7 (not part of allowed enum) at line-1, it should also fail the validation.

Is schema for each category never be the same? You can't assert uniqueness of values of object but its uniqueness of schemas.
{
"type": "object",
"properties": {
"config1": {
"type": "array",
"items": {
"type": "object",
"patternProperties": {
"\\w+": {
"oneOf": [
{"type": "object", "properties": {"a": {}}, "additionalProperties": false},
{"type": "object", "properties": {"b": {}}, "additionalProperties": false}
]
}
},
"additionalProperties": false
}
}
}
}
Update:
This is an alternative way:
{
"type": "object",
"properties": {
"config1": {
"type": "array",
"items": [
{"type": "array", "items": {"type": "string"}, "uniqueItems": true},
{"type": "array", "items": {"type": "object"}, "uniqueItems": true},
]
}
}
}
This way you can have unique keys and values. And you zip two array in your application.
This is passed:
{
"config1" : [
["category-1", "category-2"],
[{"a": 1}, {"a": 2}]
]
}
This is failed:
{
"config1" : [
["category-1", "category-2"],
[{"a": 1}, {"a": 1}]
]
}

Related

Limit type of JsonArray elements to the same type, but not limit types

We have the following JSON:
{ "someName" : [1,2,3,4,5] }
or
{ "someName" : ["one","two","three"] }
We want to draft a JSON Schema following the OpenAPI 3.x specification. Our constraint: an array element can be integer or string, but all array elements must be the same type. Our schema looks like this:
{
"type": "array",
"items": {
"oneOf": [
{"type": "string"},
{"type": "integer"}
]
}
}
This does limit the data type inside the array, but still allows to mix strings and integers in one array which we need to avoid.
{"someName" : 1, "two", "three", 4}
We looked at this question, but it didn't address consistent data type
Is there a way in OpenAPI Schema to enforce uniqueness per array?
You need to bring the oneOf up a level. Also, anyOf is a better choice in this situation. The result is the same in this case, but anyOf is more efficient.
{
"type": "array",
"anyOf": [
{ "items": { "type": "string" } },
{ "items": { "type": "integer" } },
]
}
Edit: In response to comment ...
To work around the bug, you can try pulling the type into the anyOf. The duplication is unfortunate an will make the schema less efficient, but it will probably help get around the bug.
{
"anyOf": [
{
"type": "array",
"items": { "type": "string" }
},
{
"type": "array",
"items": { "type": "integer" }
}
]
}

How do I inform a Json schema that it must have atleast one type of object in its Array, but other types are optional

I am working on updating a JSON schema for work.
For the json array, we have
"accountsInfo": [{
"type":"ADMIN",
"firstName":"Bill",
"lastName":"Cipher",
"emailAddress":"bcipher#gfalls.com"
}, {
"type":"USER"
"firstName":"Bugs",
"lastName":"Bunny",
"emailAddress":"whats#updoc.org"
}]
The USER type is needs to be optional for this schema, with the atleast 1 ADMIN type is required in the array. How can I do this?
Here is the portion of the schema file. It is using Json Schema 7.
"accountsInfo": {
"type": "array",
"uniqueItems": true,
"minItems": 2,
"items": [
{
"type": "object",
"required": [
"type",
"firstName",
"lastName",
"emailAddress"
],
"properties": {
"type": {
"type": "string",
"enum": [
"ADMIN",
"USER"
]
},
"firstName": {
"type": "string",
"$ref": "#/definitions/non-empty-string"
},
"lastName": {
"type": "string",
"$ref": "#/definitions/non-empty-string"
},
"emailAddress": {
"type": "string",
"format": "email"
}
}
}
]
}
You can use the "contains" keyword for this. In pseudocode: "the array must contain (at least one) item that successfully evaluates against this schema".
As a sibling keyword to "type": "object" and "items": { ... }, add:
"contains": {
"properties": {
"type": {
"const": "ADMIN"
}
}
}
Also, you have an error in your "items" keyword: if you intend for that subschema to match all items, not just the first, remove the extra array around the schema. The array form of "items" matches each item in the data against each item in the schema in turn, and you only specify a schema for the first item, so all items after the first can be anything.
"items": { .. schema .. } not "items": [ { .. schema .. } ].
If using the contains keyword as suggested, and if you are using strict mode, you may need to add "type": "array" like this:
{
"type": "array",
"contains": {
"properties": {
"type": {
"const": "ADMIN"
}
}
}
}

Is there a way to raise an error for any additional key present in JSON if I am using the if-else condition of JSON Schema?

I have a use case where I want to check the keys present in JSON, depending on the value of a different key.
Example JSON-1:
{
"key_name" : "value1",
"foo" : "random_value1"
}
Example JSON-2:
{
"key_name" : "value2",
"bar" : "random_value2"
}
As per these examples,
Rule 1. If the value of "key_name" is "value1" then only "foo" key should be present in JSON.
Rule 2. If the value of "key_name" is "value2", then only "bar" key should be present in JSON.
I have written the following JSON Schema for validating these JSON:
{
"type": "object",
"properties": {
"key_name": {
"type": "string",
"enum": [
"value1",
"value2"
]
},
"foo": {
"type": "string"
},
"bar": {
"type": "string"
}
},
"required": [
"key_name"
],
"additionalProperties": false,
"allOf": [
{
"if": {
"properties": {
"key_name": {
"enum": [
"value1"
]
}
}
},
"then": {
"required": [
"foo"
]
}
},
{
"if": {
"properties": {
"key_name": {
"enum": [
"value2"
]
}
}
},
"then": {
"required": [
"bar"
]
}
}
]
}
Now, as per the rules, the following JSON's are invalid, and should raise an error.
{
"key_name" : "value1",
"foo" : "random_value1",
"bar" : "random_value2"
}
OR
{
"key_name" : "value2",
"bar" : "random_value2",
"foo" : "random_value"
}
But, the above JSON Schema fails to do so.
It only checks whether "foo"/"bar" key or not, as per the value of "key_name". It fails to check for existence of any new key.
How to go about it?
This was already answered here: Mutually exclusive property groups.
Additionally, you can find a great overview here: jsonSchema attribute conditionally required.
For your specific examples, the following approaches come to mind:
Add "not": { "required": ["bar"] } to your first then clause to indicate that "bar" is not allowed. And the same for "foo" in the second then clause then.
If there is always just "key_name" and one other property allowed, you could also simply add "maxProperties": 2 in the main schema.
EDIT (to address whitelisting alternative):
Another option would be to define each permutation separately like this:
{
"oneOf": [
{
"type": "object",
"properties": {
"key_name": { "const": "value1" },
"foo": { "type": "string" }
},
"required": ["key_name", "foo"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"key_name": { "const": "value2" },
"bar": { "type": "string" }
},
"required": ["key_name", "bar"],
"additionalProperties": false
}
]
}

JSON Schema validating JSON with different property names

I am working with JSON Schema Draft 4 and am experiencing an issue I can't quite get my head around. Within the schema below you'll see an array, metricsGroups where any item should equal exactly oneOf the defined sub-schemas. Within the sub-schemas you'll notice that they both share the property name timestamp, but metricsGroupOne has the properties temperature and humidity whilst metricsGroupTwo has properties PIR and CO2. All properties within both metricsGroups are required.
Please see the schema below. Below the schema is an example of some data that I'd expect to be validated, but instead is deemed invalid and an explanation of my issue.
{
"type": "object",
"properties": {
"uniqueId": {
"type": "string"
},
"metricsGroups": {
"type": "array",
"minItems": 1,
"items": {
"oneOf": [
{
"type": "object",
"properties": {
"metricsGroupOne": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"timestamp": {
"type": "string",
"format": "date-time"
},
"temperature": {
"type": "number"
},
"humidity": {
"type": "array",
"items": {
"type": "number"
}
}
},
"additionalProperties": false,
"required": [
"timestamp",
"temperature",
"humidity"
]
}
}
},
"required": [
"metricsGroupOne"
]
},
{
"type": "object",
"properties": {
"metricsGroupTwo": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"timestamp": {
"type": "string",
"format": "date-time"
},
"PIR": {
"type": "array",
"items": {
"type": "number"
}
},
"CO2": {
"type": "number"
}
},
"additionalProperties": false,
"required": [
"timestamp",
"PIR",
"CO2"
]
}
}
},
"required": [
"metricsGroupTwo"
]
}
]
}
}
},
"additionalProperties": false,
"required": [
"uniqueId",
"metricsGroups"
]
}
Here's some data that I believe should be valid:
{
"uniqueId": "d3-52-f8-a1-89-ee",
"metricsGroups": [
{
"metricsGroupOne": [
{"timestamp": "2020-03-04T12:34:00Z", "temperature": 32.5, "humidity": [45.0] }
],
"metricsGroupTwo": [
{"timestamp": "2020-03-04T12:34:00Z", "PIR": [16, 20, 7], "CO2": 653.76 }
]
}
]
}
The issue I am facing is that both of the metricsGroup arrays in my believed to be valid data validate against both of the sub-schemas - this then invalidates the data due to the use of the oneOf keyword. I don't understand how the entry for metricsGroupOne validates against the schema for metricsGroupTwo as the property names differ and vice versa.
I'm using an node library under the hood that throws this error, but I've also tested that the same error occurs on some online validation testing websites:
jsonschemavalidator
json-schema-validator
Any help is appreciated. Thanks,
Adam
JSON Schema uses a constraints based approach. If you don't define something is not allowed, it is allowed.
What's happening here is, you haven't specificed in oneOf[1] anything which would make the first item in your instance data array invalid.
Lete me illistrate this with a simple example.
My schema. I'm going to use draft-07, but there's no difference in this principal for draft-04
{
"oneOf": [
{
"properties": {
"a": true
}
},
{
"properties": {
"b": false
}
}
]
}
And my instance:
{
"a": 1
}
This fails validation because the instance is valid when both oneOf schemas are applied.
Demo: https://jsonschema.dev/s/EfUc4
If the instance was in stead...
{
"a": 1,
"b": 1
}
This would be valid, because the instance is fails validation for the subschema oneOf[1].
If the instance was...
{
"b": 1
}
It would be valid according to oneOf[0] but not according to oneOf[1], and therefore overall would be valid because it is only valid according to one subschema.
In your case, you probably need to use additionalProperties to make properties you haven't defined in properties dissallowed. I can't tell from your question if you want to allow both properties, because your schema is defined as oneOf which seems to conflict with the instance you expect to be valid.

Appropriate behavior of JSON schema for nested required properties?

Say I have a JSON schema like this:
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"name": {"type": "string"},
"myKey": {"$ref": "myKey.json#"}
},
"additionalProperties": false
}
and then somewhere else I have myKey.json:
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object"
"properties": {
"A": {
"type": "array",
"description": "Array of stream object IDs",
"items": { "type": "integer" }
},
"B": {
"type": "array",
"description": "Array of stream object IDs",
"items": {"type": "integer" }
}
},
"required": ["A", "B"],
"additionalProperties": false
}
The important thing here is that inside myKey, there are two required properties, but myKey itself is not required. Does the
fact that myKey have required properties propagate up to the top so that myKey is forced to become required?
In other words, which of these two objects, if any, ought to be validated by this schema?
{
"name": "myName",
}
{
"name": "myOtherName",
"myKey":
{
"A": [1, 2] // Note that B is missing
}
}
The first one is valid according to the schema and the second one no.
The way to read properties tag is: if this property key is found, then it must satisfy this schema.
{
"name": "myName"
}
For the object above, myKey is not required so it satisfies the schema.
{
"name": "myOtherName",
"myKey":
{
"A": [1, 2] // Note that B is missing
}
}
For the second object, myKey is present, so it must satisfy the schema of that property. But it is not satisfied because it should have both A and B properties.
The same idea is applied to every level. The following object satisfies the schema:
{
"name": "myOtherName",
"myKey":
{
"A": [],
"B": []
}
}
But this does not:
{
"name": "myOtherName",
"myKey":
{
"A": [],
"B": ""
}
}