how to use additionalProperties from a $ref file? - json

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.

Related

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"
}
}
}
}

Conditionally Merging JSON Schema Properties

I'm trying to create a JSON schema to validate YAML for some VSCode intellisense. What I'm trying to do is choose the correct subschema to use for a property in the main schema based on an adjacent key's value.
Some JSON examples:
[
{
"name": "doesntmatter",
"matchMe": "stringToMatch:123whatever",
"mergeMe": {
"key1": "value1",
"key2": "value2"
}
}
]
[
{
"name": "doesntmatter",
"matchMe": "anotherStringToMatch:123whatever",
"mergeMe": {
"anotherKey": "valueSomething",
"anotherKey2": "cheese"
}
}
]
So I need to choose the correct schemas for the mergeMe objects based on the substring match of matchMe. After following a bunch of answers, I'm at a point where I can either make it match multiple, and error my linter, or match none, but an online validator says it's ok (except nothing matches as the required fields aren't triggering).
I moved my sub-schemas to be merged into definitions to reference them, and then used an if/then to match. That worked with one, but then I tried to expand it to do the tree matching, and I can't get that to work. Someone said that I should wrap my if/thens in an allOf (I'm not sure why that would work since surely not all of them would match?). Changing it to an anyOf makes none of them match and I get no intellisense. Nor do I really understand why I should wrap single if/thens or thens in allOfs.
The idea is that based on the pattern it uses a definitions schema to match the mergeMe property, but the conditional logic isn't quite right. Thinned schema below:
{
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "http://example.com/example.json",
"type": "array",
"title": "The root schema",
"description": "The root schema comprises the entire JSON document.",
"default": [],
"additionalItems": true,
"definitions": {
"stringToMatch": {
"$id": "#/definitions/stringToMatch",
"type": "object",
"properties": {
"key1": {
"type": "string"
}
},
"required": [
"key1"
],
"additionalProperties": true
},
"anotherStringToMatch": {
"$id": "#/definitions/anotherStringToMatch",
"type": "object",
"properties": {
"key2": {
"type": "string"
}
},
"required": [
"key2"
],
"additionalProperties": true
}
},
"items": {
"$id": "#/items",
"type": "object",
"title": "main schema",
"description": "An explanation about the purpose of this instance.",
"default": {},
"examples": [],
"required": [
"name",
"matchMe",
"mergeMe"
],
"properties": {
"name": {
"$id": "#/items/name",
"type": "string",
"title": "The name schema",
"description": "An explanation about the purpose of this instance.",
"default": "",
"examples": []
},
"matchMe": {
"$id": "#/items/matchMe",
"type": "string",
"title": "The matchMe schema",
"description": "An explanation about the purpose of this instance.",
"default": "",
"examples": []
}
},
"allOf": [
{
"if": {
"properties": {
"matchMe": {
"pattern": "^stringToMatch:[0-9.]+"
}
}
},
"then": {
"allOf": [
{
"type": "object",
"properties": {
"mergeMe": {
"$ref": "#/definitions/stringToMatch"
}
}
}
]
}
},
{
"if": {
"properties": {
"gear": {
"pattern": "^anotherStringToMatch:[0-9.]+"
}
}
},
"then": {
"allOf": [
{
"type": "object",
"properties": {
"mergeMe": {
"$ref": "#/definitions/anotherStringToMatch"
}
}
}
]
}
}
],
"additionalProperties": true
}
}
What I want in JS would look something like
const schema = { name, matchMe }
if (matchMe == "string1") schema.mergeMe = ...subschema1;
else if (...)
else if (...)
but I just can't really work it out. Can someone help?
Edit: jsonschema.dev playground - the idea being if I specify the food as prefixed by "fruit" I have to give it "pips" and "berry", whereas if I specify "vegetable" I have to give it a totally differet schema, and they don't overlap.
https://jsonschema.dev/s/pHzGo
This actually ended up being a bug in the VSCode YAML extension that was ingesting my schema, causing the if blocks to not evaluate, and has been raised, fixed and released.

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"
]
}
}
}
}
]
}

Using multiple anyOf inside oneOf

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.