Using multiple anyOf inside oneOf - json

I wanted to create a schema where I will be having multiple objects inside "oneOf" which will be having many objects in anyOf format where some of the keys can be of required type(this part works)
My schema :-
{
"description": "schema v6",
"type": "object",
"oneOf": [
{
"properties": {
"Speed": {
"items": {
"anyOf": [
{
"$ref": "#/definitions/speed"
},
{
"$ref": "#/definitions/SituationType"
}
]
},
"required": [
"speed"
]
}
},
"additionalProperties": false
}
],
"definitions": {
"speed": {
"description": "Speed",
"type": "integer"
},
"SituationType": {
"type": "string",
"description": "Situation Type",
"enum": [
"Advice",
"Depend"
]
}
}
}
But when I'm trying to verify this schema but i'm able to authenticate some incorrect values like
{
"Speed": {
"speed": "ABC",//required
"SituationType1": "Advisory1" //optional but key needs to be correct
}
}
correct response which i was expecting was
{
"Speed": {
"speed": "1",
"SituationType": "Advise"
}
}

First, you need to set the schema type correctly, otherwise implmentations may assume you're using the latest JSON Schema version (currently draft-7).
So, in your schema root, you need the following:
"$schema": "http://json-schema.org/draft-06/schema#",
Second, items is only applicable if the target is an array.
Currently your schema only checks the following:
If the root object has a property of "Speed", it must have a key of
"speed". The root object must not have any other properties.
And nothing else.
Your use of definitions and how you reference them is probably not what you intended.
It looks like you want Speed to contain speed which must be an integer, and optionaly SituationType which must be a string, limited by enum, and nothing else.
Here's the schema I have based on that, which passes and fails correctly based on your given example data:
{
"$schema": "http://json-schema.org/draft-06/schema#",
"type": "object",
"oneOf": [
{
"properties": {
"Speed": {
"properties":{
"speed": {
"$ref": "#/definitions/speed"
},
"SituationType": {
"$ref": "#/definitions/SituationType"
}
},
"required": [
"speed"
],
"additionalProperties": false
}
},
"additionalProperties": false
}
],
"definitions": {
"speed": {
"description": "Speed",
"type": "integer"
},
"SituationType": {
"type": "string",
"description": "Situation Type",
"enum": [
"Advice",
"Depend"
]
}
}
}
You need to define the properties for Speed, because otherwise you can't prevent additional properties, as additionalProperties is only effected by adjacent an properties key. We are looking to created a new keyword in draft-8 to support this kind of behaviour, but it doesn't look like you need it in your example (Huge Github issue in relation).
Adding additionalProperties false to the Speed schema now prevents other keys in that object.
I SUSPECT that given your question title, there may be more schema at play here, and you've simplified it for this question. If you have a more detailed schema with more complex issues, I'd be happy to help also.

Related

Why required is not an element property?

Current schema example:
{
"$schema": "http://json-schema.org/schema#",
"type": "object",
"properties": {
"id": {
"type": "string",
"uniqueItems": true
},
"name": {
"type": "string"
},
"age": {
"type": "number"
},
"description": {
"type": "string"
}
},
"required": ["id", "name", "age"]
}
This to me is counterintuitive. It requires to repeat the property names and repetition is bad. I would have expected this instead:
{
"$schema": "http://json-schema.org/schema#",
"type": "object",
"properties": {
"id": {
"type": "string",
"uniqueItems": true,
"required": true
},
"name": {
"type": "string",
"required": true
},
"age": {
"type": "number",
"required": true
},
"description": {
"type": "string"
}
}
}
Is there a technical reason for required being an array where you have to repeat the property names? Is this approach superior in any way?
The set of required keys is an attribute of an object, not of its individual properties. That is, a predefined property
{
...
"$defs": {
"age_property": {
"type": "number"
}
}
...
}
may be required by one object
{
"type": "object",
"properties": {
"age": { "$ref": "#/$defs/age_property" },
...
},
"required": ["age", ...]
}
but not another
{
"type": "object",
"properties": {
"age": { "$ref": "#/$defs/age_property" },
...
},
"required": [...]
}
tl;dr
It has to do with what the keyword is actually evaluating. It's evaluating the container for the property's presence; the subschema in /properties is checking the value, if there is one.
Explanation
(source: I'm one of the specification authors and a validator implementor)
required used to be a keyword that was contained inside a property definition. As of draft 4, it was moved to it's own root-level keyword.
The value inside properties is always to be a schema. This subschema should stand alone, unaware that it's contained within a larger schema. As a schema, its function is to evaluate a value, but it has no knowledge of the origin of the value. In the case of properties, this is a value from a key-value pair. Again, it has no knowledge of the key or the object that contains it.
If required were part of the property definition, it would be validating not the value of the property, but the object that contains it. This is the responsibility of the parent schema.
An example:
// schema
{
"type": "object",
"properties": {
"a": { "type": "string" },
"b": { "required": true }
}
}
// instance
{ "b": "some value" }
/properties/b ({"required":true}) is instructed to evaluate "some value". How can required know that this value comes from an object and is under the b property? It would need knowledge of the value's parent to do that. (JSON Schema validators had to bend themselves into funny shapes in order to support this.)
The solution was to move required out of the property and into the schema that is evaluating the object itself.
// schema
{
"type": "object",
"properties": {
"a": { "type": "string" }
},
"required": [ "b" ]
}
// instance
{ "b": "some value" }
Now, required can evaluate the full object, and it can check whether that object contains a b property. Because there is no /properties/b in this case, any value is fine, so long as b is present.
Unfortunately, the discussion around moving this keyword has been lost as the current GitHub repo was set up after the move from draft 3 to draft 4.
The written specification is not based on real world application. See https://github.com/json-schema-org/json-schema-spec/issues/725 from the one who's probably done most practical use of json schema (ajv lib's author).
There is no right or wrong about the approach, but it is going to be useful for wide range of application or not is questionable. There are TONS of debates around this specification.
IMO, yes required makes impossible state possible (out-of-sync)
there is no technical reason, who designed jsonschema decided to use an array instead of element properties, pheraps because in this way you have all the required elements name near standing.

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 use additionalProperties from a $ref file?

ive separated my JSON schemas into two files.
person-input.json (all properties that will be set by inputs.)
person.json (hold a ref to person-input.json but also have dateUpdate, dateCreated and DateDeleted).
This to separate the inputs from autogenerated date properties.
I do not want any posts to be able to add unwanted properties to my data, so i thought i would use "additionalProperties": false the problem is that if i use it in the person-input.json file it will not accept my "date" properties from the person.json file. And if i put it in the person.json file it doesn't stop random properties from being added. Is there a solution to this?
so this below dont work, have i missplaced the "additionalProperties": false ?
person.json
{
"allOf": [
{
"$ref": "./person-input.json"
},
{
"type": "object",
"properties": {
"dateCreated": {
"name": "dateCreated",
"type": "string",
"description": "date created",
"example": "2019-09-02T11:17:41.783Z"
},
"dateUpdated": {
"type": "string",
"nullable": true,
"description": "date updated",
"example": "2019-09-02T11:17:41.783Z"
},
"dateDeleted": {
"type": "string",
"nullable": true,
"description": "date deleted",
"example": "2019-09-02T11:17:41.783Z"
}
},
"additionalProperties": false
}
]
}
additionalProperties cannot "see through" applicators like allOf, nor can it "see across" the use of $ref.
In order to fix this, you have to do SOME duplication of your schema in your outer most / top most schema, and remove the additionalProperties: false requirement from any child schemas.
additionalProperties: false works by applying false (which is a valid schema, which returns failure to validate) to values which do not match keys based on properties or patternProperties within the SAME schema object.
Validation with "additionalProperties" applies only to the child
values of instance names that do not match any names in "properties",
and do not match any regular expression in "patternProperties".
https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-validation-01#section-6.5.6 (draft-7)
So, if you need to copy all the properties you need to the top level schema. Yes this is not nice!
You can make it a little nicer by observing the fact that the values of a properties object are schemas, and as such may just be true, allowing the child schemas to actually DO the validation later.
Here's an example I'm going to be using in an upcoming talk:
{
"$schema": "http://json-schema.org/draft-07/schema",
"title": "MatchMakerExchange format for queries",
"definitions": {
"phenotypicFeatures": {
"type": [
"array"
]
},
"genomicFeatures": {
"type": [
"array"
]
},
"geneticsPatient": {
"properties": {
"phenotypicFeatures": {
"$ref": "#/definitions/phenotypicFeatures"
},
"genomicFeatures": {
"$ref": "#/definitions/genomicFeatures"
}
},
"anyOf": [
{
"required": [
"phenotypicFeatures"
]
},
{
"required": [
"genomicFeatures"
]
}
]
},
"regularPatient": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": [
"string"
]
}
}
}
},
"properties": {
"patient": {
"additionalProperties": false,
"properties": {
"name": true,
"phenotypicFeatures": true,
"genomicFeatures": true
},
"allOf": [
{
"$ref": "#/definitions/regularPatient"
},
{
"$ref": "#/definitions/geneticsPatient"
}
]
}
}
}
You may well ask... "Well, that's crazy. Can you fix this please?" - We did. It's called draft 2019-09, and it's only recently been released, so you'll have to wait for implementations to catch up.
A new keyword, unevaluatedProperties depends on annotation results, but you'd still need to remove additionalProperties: false from child schemas.

JSON Schema validator with an array of specific objects (different types)

I have the following JSON data that I would like to validate.
[
{ "fieldType": "oneThing" },
{ "fieldType": "anotherThing" },
{ "fieldType": "oneThing" }
]
And my current (non working) schema is:
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "array",
"items": {
"oneOf": [
{ "$ref": "#/definitions/oneThing" },
{ "$ref": "#/definitions/anotherThing" }
]
},
"definitions": {
"oneThing": {
"type": "object",
"properties": {
"fieldType": {
"type": "string",
"pattern": "oneThing"
}
},
"required": [
"fieldType"
]
},
"anotherThing": {
"type": "object",
"properties": {
"fieldType": {
"type": "string",
"pattern": "anotherThing"
}
},
"required": [
"fieldType"
]
}
}
}
I'm getting the following error but I fail to see what I'm doing wrong.
[] Object value found, but an array is required
More context: I'm generating a dynamic HTML form based on a JSON configuration. The HTML form will have a specific set of valid field types and the same field type may exist multiple times in the config, thus oneThing appearing more than once in the above sample json.
As it turns out, this had nothing to do with my JSON schema but with how I was calling the library that was parsing the schema.
I'm using https://github.com/justinrainbow/json-schema and was passing the wrong data type to the class. Duh!

How to tell JSON schema validator to pick schema from property value?

For example a schema for a file system, directory contains a list of files. The schema consists of the specification of file, next a sub type "image" and another one "text".
At the bottom there is the main directory schema. Directory has a property content which is an array of items that should be sub types of file.
Basically what I am looking for is a way to tell the validator to look up the value of a "$ref" from a property in the json object being validated.
Example json:
{
"name":"A directory",
"content":[
{
"fileType":"http://x.y.z/fs-schema.json#definitions/image",
"name":"an-image.png",
"width":1024,
"height":800
}
{
"fileType":"http://x.y.z/fs-schema.json#definitions/text",
"name":"readme.txt",
"lineCount":101
}
{
"fileType":"http://x.y.z/extended-fs-schema-video.json",
"name":"demo.mp4",
"hd":true
}
]
}
The "pseudo" Schema note that "image" and "text" definitions are included in the same schema but they might be defined elsewhere
{
"id": "http://x.y.z/fs-schema.json",
"definitions": {
"file": {
"type": "object",
"properties": {
"name": { "type": "string" },
"fileType": {
"type": "string",
"format": "uri"
}
}
},
"image": {
"allOf": [
{ "$ref": "#definitions/file" },
{
"properties": {
"width": { "type": "integer" },
"height": { "type": "integer"}
}
}
]
},
"text": {
"allOf": [
{ "$ref": "#definitions/file" },
{ "properties": { "lineCount": { "type": "integer"}}}
]
}
},
"type": "object",
"properties": {
"name": { "type": "string"},
"content": {
"type": "array",
"items": {
"allOf": [
{ "$ref": "#definitions/file" },
{ *"$refFromProperty"*: "fileType" } // the magic thing
]
}
}
}
}
The validation parts of JSON Schema alone cannot do this - it represents a fixed structure. What you want requires resolving/referencing schemas at validation-time.
However, you can express this using JSON Hyper-Schema, and a rel="describedby" link:
{
"title": "Directory entry",
"type": "object",
"properties": {
"fileType": {"type": "string", "format": "uri"}
},
"links": [{
"rel": "describedby",
"href": "{+fileType}"
}]
}
So here, it takes the value from "fileType" and uses it to calculate a link with relation "describedby" - which means "the schema at this location also describes the current data".
The problem is that most validators do not take any notice of any links (including "describedby" ones). You need to find a "hyper-validator" that does.
UPDATE: the tv4 library has added this as a feature
I think cloudfeet answer is a valid solution. You could also use the same approach described here.
You would have a file object type which could be "anyOf" all the subtypes you want to define. You would use an enum in order to be able to reference and validate against each of the subtypes.
If the sub-types schemas are in the same Json-Schema file you don't need to reference the uri explicitly with the "$ref". A correct draft4 validator will find the enum value and will try to validate against that "subschema" in the Json-Schema tree.
In draft5 (in progress) a "switch" statement has been proposed, which will allow to express alternatives in a more explicit way.