Future proof JSON schema - json

Is it possible to force known objects ("enemy" and "friend") to be defined while other objects are allowed?
I've added the last object {"type": "object"} to display the intended behaviour - but in reality the last object will overrule the two defined objects ("enemy" and "friend") causing any kind of object to be valid with this schema. If I remove the last object, it will allow the two objects but nothing else.
JSON schema (using array for faster testing):
{
"type": "array",
"items": {
"anyOf": [
{"$ref": "#/definitions/friend"},
{"$ref": "#/definitions/enemy"},
{"$ref": "#/definitions/future"}
]
},
"definitions": {
"friend": {
"type": "object",
"properties": {
"time": {"type": "string"},
"value": {"type": "number", "minimum": 100}
},
"required": ["time", "value"],
"additionalProperties": false
},
"enemy": {
"type": "object",
"properties": {
"enemy": {"type": "string"},
"color": {"type": "number"}
},
"required": ["enemy", "color"],
"additionalProperties": false
},
"future": {
"type": "object",
"properties": {
"time": {"type": "string"}
}, "required": ["time"],
"additionalProperties": true
}
}
}
Example JSON (top 3 should be OK, last 3 should not be OK):
[
{"time": "123", "value": 100}, <- should be valid
{"time": "1212", "value": 150}, <- should be valid
{"enemy": "bla", "color": 123}, <- should be valid
{"time": "1212", "value": 50}, <- should be invalid bcoz of "future"
{"enemy": "bla", "color": "123"}, <- shouldn't be valid bcoz of "enemy" schema
{"somethingInFuture": 123, "someFutProp": "ok"} <- should be valid
]

It's not very clear what you really want but you probably want to use a combination of dependencies and minProperties here. The two forms of this keyword are used here: property dependency and schema dependency. You also want at least one property to be defined, hence minProperties. So you can do something like this (note that I also factorized the common schema for color; it may, or may not, be what you want):
{
"type": "object",
"minProperties": 1,
"additionalProperties": {
"type": "string" // All properties not explicitly defined are strings
},
"properties": {
"color": { "type": "number" }
},
"dependencies": {
"enemy": "color", // property dependency
"friend": { // schema dependency
"properties": { "color": { "minimum": 50 } },
"required": [ "color" ]
}
}
}
Note that this schema still allows "enemy" and "friend" at the same time (so does your original schema). To do better, sample JSONs you want to deem valid and invalid should be provided.

Related

How do I represent polymorphic objects in JSON schema?

I'm trying to create a schema for a JSON object that varies its schema depending on the value of one of its properties: type.
Like this:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["INT", "PERCENT"]
}
},
"required": ["type"],
"allOf": [
{
"if": {
"properties": {"type": {"const": "INT"}}
},
"then": {
"properties": {
"value": {"type": "number", "multipleOf": 1}
},
"required": ["value"],
"additionalProperties": false
}
},
{
"if": {
"properties": {"type": {"const": "PERCENT"}}
},
"then": {
"properties": {
"value": {"type": "number"},
"min": {"type": "number"},
"max": {"type": "number"}
},
"required": ["value", "min", "max"],
"additionalProperties": false
}
}
]
}
But I'm getting all sorts of misbehaviour from the various validators I'm trying.
Some examples with problems noted after the //:
{
"type": "PERCENT", // property type has not been defined(?!)
"value": 0.0,
"min": 10,
"max": 25
}
{
"type": "INT", // no errors allowed, and value
"value": 0.1, // should've been flagged as not multiple of 1
"min": 10, // should've been flagged as disallowed additional property
"max": 25 // same as above
}
Here's the validator I'm trying
Thanks in advance for your help!
It seems that you've figured out that the problem is that addtionalProperties only considers the properties defined in the sub-schema it's defined in. That's why you have to include the "type" property in your then. You have a few options that are an improvement over the solution you posted that you weren't happy with.
Option 1: unevaluatedProperties
Draft 2019-09 introduced the unevaluatedProperties keyword. If you use this at the top level of your schema and don't use addtionalProperties anywhere, you will get the behavior you expected from additionalProperties. I see you are using draft-07, so you have to upgrade in order to use this solution.
Option 2: Boolean schemas
The solution you came up with is to re-define the "type" property in the then schemas. additionalProperties just needs "type" to be declared, you don't need to include the schema for it again. You can use true or the empty schema {} instead.
Option 3: propertyNames
Instead of additionalProperties you can use propertyNames. This allows you to declare a list of which property names are allowed in the object rather than hacking additionalProperties with true or {} to figure out the allowed property names the way you want it to.
well I found a way to get what I wanted, but it doesn't mean I like it. Having to redefine "type" doesn't seem right to me.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["INT", "PERCENT"]
}
},
"required": ["type"],
"allOf": [
{
"if": {
"properties": {"type": {"const": "INT"}}
},
"then": {
"properties": {
"value": {"type": "number", "multipleOf": 1},
"type": {"type": "string"}
},
"required": ["value", "type"],
"additionalProperties": false
}
},
{
"if": {
"properties": {"type": {"const": "PERCENT"}}
},
"then": {
"properties": {
"value": {"type": "number"},
"min": {"type": "number"},
"max": {"type": "number"},
"type": {"type": "string"}
},
"required": ["value", "min", "max", "type"],
"additionalProperties": false
}
}
]
}
tests...
This validates:
{
"type": "PERCENT",
"value": 0,
"min": 10,
"max": 20
}
so does this:
{
"type": "INT",
"value": 0
}
this errors in the way I want:
{
"type": "INT",
"value": 0.1, // not a multiple of 1
"min": 4, // not expected
"max": 5 // not expected
}
and so does this:
{
"type": "PERCENT",
"value": 0.1,
"max": 5 // min is a required property
}

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.

How to combine a property type, to match another properties type

I have the following use case with a JSON schema. I have a metadata object of a setting. In our case a setting can be of type string/real/integer/boolean.
In this object I have 4 fields: default/minimum/maximum each define a property of the setting.
Now what I want to achieve is that when the type of de default value is an integer, also the minimum/maximum values are integers.
The schema I have come up with so far:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"setting-value": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
}
]
},
"setting-meta": {
"type": "object",
"required": [
"name",
"type",
"default"
],
"properties": {
"name": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"Real",
"Integer",
"Boolean",
"String"
]
},
"minimum": {
"$ref": "#/definitions/setting-value"
},
"maximum": {
"$ref": "#/definitions/setting-value"
},
"default": {
"$ref": "#/definitions/setting-value"
},
"value": {
"$ref": "#/definitions/setting-value"
}
}
}
}
}
Here it is possible for the #/definitions/setting-meta to have support for the different types. However it does not define that if for example the value of TYPE is equal to Real/Integer that the types of minimum/maximum/default/value should all be of type number.
I would use these definitions as follows
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$ref": "schema-definition-above.json#/definitions/setting-meta"
}
According the the current schema, all examples below are VALID, however they should be valid/invalid as suggested:
Valid JSon object:
{
"name": "Enabled",
"type": "Boolean",
"minimum": false,
"maximum": true,
"default": true,
"value": true
}
Invalid json object, minimum/maximum/default don't have the same type:
{
"name": "Enabled",
"type": "Boolean",
"minimum": false,
"maximum": 1,
"default": "value",
"value": true
}
Invalid json object: type, does not match the actual type of the values
{
"name": "Enabled",
"unit": "enabled/disabled",
"configId": "Accumulator",
"displayName": "Enable or disable this machine",
"type": "Integer",
"minimum": false,
"maximum": true,
"default": true,
"value": true
}
My question:
Is it possible to put these kinds of dependencies into a JSON schema? The only kind of dependency I have foudn so far is with property dependencies indicating that if one property is set, another should also be set.
Any help would be much appreciated.
EDIT:
Extended that use case with some JSON objects that should be validated or invalidated with the referenced schema.
In order to do conditional validation where you have a known set of possible conditions, you should use the if/then/else keywords, in combination with with allOf.
In this schema, the first schema in allOf defines your general structure and overall requirements. The second schema applies the then constraint if the if schema validates successfully.
You would need to replicate the second schema for each condition that you have.
You can see this schema working at https://jsonschema.dev (link is preloaded with the below schema and sample data)
(The use of patternProperties is just a space saver. You could define each property individually.)
{
"$schema": "http://json-schema.org/draft-07/schema#",
"allOf": [
{
"properties": {
"type": {
"enum": [
"Real",
"Integer",
"Boolean",
"String"
]
}
},
"required": [
"type",
"default",
"name"
]
},
{
"if": {
"properties": {
"type": {
"const": "String"
}
}
},
"then": {
"patternProperties": {
"^(minimum|maximum|default|value)$": {
"type": [
"string"
]
}
}
}
}
]
}

Json schema to validate object's values against content of another object

I'm trying to create json schema for a document where field values in some object should validate against a enum defined in another object in the same document.
More specifically, in the example below, I'd like to be able to define "properties" with id and values (I should be able to define different properties in different json files).
Then "objects" should be able to refer to these properties, so that object.properties[i].id must match with id of one of the properties and object.properties[i].value must match with one of the enum values defined for that property.
{
"properties": [
{
"id": "SIZE",
"values": ["small", "medium", "big"]
},
{
"id": "MATERIAL",
"values": ["wood", "glass", "steel", "plastic"]
},
{
"id": "COLOR",
"values": ["red", "green", "blue"]
}
],
"objects": [
{
"name": "chair",
"properties": [
{
"id": "SIZE",
"value": "small"
},
{
"id": "COLOR",
"value": "red"
}
],
},
{
"name": "table",
"properties": [
{
"id": "MATERIAL",
"value": "wood"
}
]
}
]
}
I tried to create json schema to validate such structure, but got stuck with describing reference to inner fields of "property" object. I also looked into the standard and did not find a way to achieve the goal.
Is it possible to create a json schema which would validate my json files?
There is a proposal for $data reference that almost allows to do it if you change your data structure a little bit to remove one level of indirection. It's is supported in Ajv (I am the author).
So if your data were:
{
"properties": {
"SIZE": ["small", "medium", "big"],
"MATERIAL": ["wood", "glass", "steel", "plastic"],
"COLOR": ["red", "green", "blue"]
},
"objects": {
"chair": {
"SIZE": "small",
"COLOR": "red"
},
"table": {
"MATERIAL": "wood"
}
}
}
then your schema could have been:
{
"type": "object",
"properties": {
"properties": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": { "type": "string" }
}
},
"objects": {
"type": "object",
"additionalProperties": {
"type": "object",
"properties": {
"SIZE": {"enum": {"$data": "3/properties/SIZE"}},
"MATERIAL": {"enum": {"$data": "3/properties/MATERIAL"}},
"COLOR": {"enum": {"$data": "3/properties/MATERIAL"}}
}
}
}
}
}
And it could be dynamically generated based on all list of possible properties.
With the data structure you have you either can use custom keywords if the validator supports them or implement some part of validation logic outside of JSON schema.

Is "type" optional in Json schema

{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Product set",
"type": "array",
"items": {
"title": "Product",
"type": "object",
"properties": {
"id": {
"description": "The unique identifier for a product",
"type": "number"
},
"name": {
"type": "string"
},
"price": {
"type": "number",
"minimum": 0,
"exclusiveMinimum": true
},
"tags": {
"type": "array",
"items": {
"type": "string"
},
"minItems": 1,
"uniqueItems": true
},
"dimensions": {
"type": "object",
"properties": {
"length": {"type": "number"},
"width": {"type": "number"},
"height": {"type": "number"}
},
"required": ["length", "width", "height"]
},
"warehouseLocation": {
"description": "Coordinates of the warehouse with the product",
"$ref": "http://json-schema.org/geo"
}
},
"required": ["id", "name", "price"]
}
}
In the above Json schema "dimensions"is of "type": "object" , is type optional , if "type" is not specified should i assume it to be object. Could not find anything in specs related to optional elements.
https://datatracker.ietf.org/doc/html/draft-zyp-json-schema-03#section-5.1
If the property is not defined or is not in this list, then any
type of value is acceptable.
type is optional, ANY value is acceptable if type is omitted.
I think it is not optional. As you can see in the meta-schema below, the property "type" has no default value:
http://json-schema.org/schema
Also in my JSON Schema library NJsonSchema I set the type to None instead of Object. Check out the TypeRaw property:
https://github.com/rsuter/NJsonSchema/blob/master/NJsonSchema/JsonSchema4.Serialization.cs
When the default value is set to Object a lot more tests from the JSON Schema test suite fail. Maybe you can find a definite answer in this JSON Schema test suite:
https://github.com/json-schema/JSON-Schema-Test-Suite