I have two types defined by a JSON Schema:
Type A
{
"type": "object",
"properties": {
"a": { "type": "string" }
}
}
Type X
{
"type": "object",
"properties": {
"x": { "type": "string" }
}
}
I want to create a schema combining A and X, but without additional properties. (only properties 'a' and/or 'x' should be present). I tried to use "allOf" but I can't add a restriction about additional properties. If I add "additionalProperties" in A or X, it doesn't work.
How should I process ? Of course, I don't want to repeat A into X
What you're looking for is the unevaluatedProperties feature available in jsonschema 2019-09 upwards. In contrast to additionalProperties, which only concerns the locally defined properties, unevaluatedProperties works across sub-schemas (but is more expensive to evaluate).
The schema
{
"anyOf": [
{
"type": "object",
"properties": {
"a": {
"type": "string"
}
}
},
{
"type": "object",
"properties": {
"x": {
"type": "string"
}
}
}
],
"unevaluatedProperties": false
}
Allows
{
"a": "foo",
"x": "bar"
}
But disallows
{
"a": "foo",
"x": "bar",
"t": "baz"
}
If you're stuck with an earlier jsonschema version, you have to manually define the properties of the sub-schemas in the parent like this:
{
"anyOf": [
{
"type": "object",
"properties": {
"a": {
"type": "string"
}
}
},
{
"type": "object",
"properties": {
"x": {
"type": "string"
}
}
}
],
"properties": {
"a": true,
"x": true
},
"additionalProperties": false
}
Related
In JSON Schema, I can use require to ensure that a property exists on the same level of the hierarchy, but I'm having trouble validating for nested ones.
Suppose I have following JSON Schema:
{
"type": "object",
"properties": {
"my_type": {
"type": "string"
},
"t1_data": {
"type": "object",
"properties": {
"id": {
"type": "string"
}
}
},
"t2_data": {
"type": "object",
"properties": {
"id": {
"type": "string"
}
}
}
}
}
How would I specify the following validations?
if my_type == "type1", then t1_data.id must exist
if my_type == "type2", then t2_data.id must exist
if my_type is anything else, validation passes
I've tried using the require and anyOf constructs but I could only get them to work at the same level of the hierarchy.
Thanks,
A possible solution is to combine allOf and if-then. It is a little bit verbose but I am not aware of any shorter way. Here is the schema for the case "type1":
{
"$schema": "https://json-schema.org/draft/2019-09/schema",
"description": "JSON schema generated with JSONBuddy https://www.json-buddy.com",
"type": "object",
"properties": {
"my_type": {
"type": "string"
},
"t1_data": {
"type": "object",
"properties": {
"id": {
"type": "string"
}
}
},
"t2_data": {
"type": "object",
"properties": {
"id": {
"type": "string"
}
}
}
},
"allOf": [
{
"if": {
"properties": {
"my_type": {
"const": "type1"
}
}
},
"then": {
"properties": {
"t1_data": {
"type": "object",
"properties": {
"id": {
"type": "string"
}
},
"required": [ "id" ]
}
}
}
}
]
}
"type2" would be quite the same as next schema in the allOf array.
So my json structure is aspect oriented, meaning that the json is structure in a way that each data is represented by a key and that key will define the structure of its content.
for example:
[
{
"nv": [{ "ln": 123 }]
},
{
"metadata": [{ "name": "nodes" }, { "name": "edges" }]
},
{
"nodes": [{ "#id": 1 }, { "#id": 2 }]
},
{
"edges": [
{ "#id": 1, "nodeId": 1 },
{ "#id": 2, "nodeId": 2 }
]
},
{
"status": [{ "success": true }]
}
]
As shown 3 objects (nv, metadata, status) and based on the name inside of the metadata there will be objects inside the json file.
I tried something like this:
{
"type": "array",
"items": [
{
"type": "object",
"properties": {
"nv": { "type": "array", "items": { "$ref": "#definitions/nv" } }
},
"required": ["nv"]
},
{
"type": "object",
"properties": {
"metaData": {
"type": "array",
"items": { "$ref": "#definitions/metadata" }
}
},
"required": ["metaData"]
},
{
"anyOf": [
{
"type": "object",
"properties": {
"nodes": {
"type": "array",
"items": { "$ref": "#definitions/nodes" }
}
}
},
{
"type": "object",
"properties": {
"edges": {
"type": "array",
"items": { "$ref": "#definitions/edges" }
}
}
},
{
"type": "object",
"properties": {
"edgeAttribute": {
"type": "array",
"items": { "$ref": "#definitions/edgeAttribute" }
}
}
},
{
"type": "object",
"properties": {
"nodeAttribute": {
"type": "array",
"items": { "$ref": "#definitions/nodeAttribute" }
}
}
}
]
},
{
"type": "object",
"properties": {
"status": {
"type": "array",
"items": { "$ref": "#definitions/status" }
}
},
"required": ["status"]
}
],
"definitions": {
"status": {
"type": "object",
"properties": {
"success": { "type": "boolean" }
}
"etc..."
}
}
}
but then if I define an empty array it will be accepted, also it is being accepted if the array only contains one of the 3 required objects.
So is there a way to validate against something like the example using json-schemas?
The real scenario may have more than just 2 objects inside of the metadata that's why I did not use if -> then -> else conditions. if the solution is by using them then please let me know.
The structure of the data makes this a rough one, but there are a few patterns you can use to get the behavior you want. Let's take them one at a time.
Declare an array that can contain any of a number of objects
Generally people use oneOf for this, but I don't recommend that because it can have poor performance and incomprehensible error messages. Usually that means if/then, but in this case you can get good results by defining your items as a single object that only allows one property at a time in each object.
{
"items": {
"type": "object",
"properties": {
"nv": { "$ref": "#/definitions/nv" },
"metadata": { "$ref": "#/definitions/metadata" },
"status": { "$ref": "#/definitions/status" },
"nodes": { "$ref": "#/definitions/nodes" },
"edges": { "$ref": "#/definitions/edges" }
},
"minProperties": 1,
"maxProperties": 1
}
}
Edit: Previously, I recommended dependencies, but then realized that this is better.
Assert that the array contains a required object
To do this, you need to assert that the array contains an object that has a required property.
{ "contains": { "type": "object", "required": ["nv"] } }
You'll have to combine this pattern in allOf to express additional required items.
{
"allOf": [
{ "contains": { "type": "object", "required": ["nv"] } },
{ "contains": { "type": "object", "required": ["metadata"] } },
{ "contains": { "type": "object", "required": ["status"] } }
]
}
Conditionally assert that the array contains a required object
The tricky part here is getting all the nested contains and properties in the if to be able to assert that the "name" is a certain value. The then just uses the same pattern we used above to assert than an object is required in the array.
{
"if": {
"type": "array",
"contains": {
"type": "object",
"properties": {
"metadata": {
"type": "array",
"contains": {
"type": "object",
"properties": {
"name": { "const": "nodes" }
},
"required": ["name"]
}
}
},
"required": ["metadata"]
}
},
"then": { "contains": { "type": "object", "required": ["nodes"] } }
}
The above example shows the "nodes" object being conditionally required. You'll need to repeat this pattern for the "edges" object and combine them with allOf. I suggest making use of definitions to help make this readable.
{
"allOf": [
{ "$ref": "#/definitions/if-metadata-has-nodes-then-require-nodes-object" },
{ "$ref": "#/definitions/if-metadata-has-edges-then-require-edges-object" }
]
}
I would suggest moving each of your "types" into a $defs to be referenced.
{
"$defs": {
"nvObj": {
"type": "object",
"properties": {
"nv": { "type": "array", "items": { "$ref": "#/$defs/nv" } }
},
"required": ["nv"]
},
... // other defitions
}
}
(I've updated the $ref to use $defs instead of definitions as this is the new keyword since draft 7.)
Then you can put many references into a oneOf.
{
"$defs": {
... // from above
},
"type": "array",
"items": {
"oneOf": [
{ "$ref": "#/$defs/nvObj" },
... // all of the other object definitions
]
}
}
You're right to avoid if/then/else for this case. oneOf is the best bet here.
What I am asking myself is if I can cascade multiple 'oneOf' or maybe there is a better way to make my Cases Valid.
I am Trying to validate the following:
Use definition of ObjectA or ObjectB as Single Objects or an Array of a them
Case 1:
Using only definition of ObjectA
{
"X": "test"
}
Case 2:
Using only definition of ObjectB
{
"Y": "test"
}
Case 3:
Using definition of ObjectA or ObjectB in an Array
[
{
"X": "test"
},
{
"Y": "test"
}
]
Case 4:
Using definition of ObjectA twice in an Array
[
{
"X": "test"
},
{
"X": "test"
}
]
Schema:
I tryed using this schema, the IntelliSense of MonacoEditor is working well but I still get the Error/Warning: "Matches multiple schemas when only one must validate."
{
"definitions": {
"objectA": {
"type": "object",
"properties": {
"X": {
type: "string"
}
}
},
"objectB": {
"type": "object",
"properties": {
"Y": {
type: "string"
}
}
}
},
"oneOf":
[
{
"oneOf":
[
{
"$ref": "#definitions/objectA"
},
{
"$ref": "#definitions/objectB"
}
]
},
{
"type": "array",
"items":
{
"oneOf":
[
{
"$ref": "#definitions/objectA"
},
{
"$ref": "#definitions/objectB"
}
]
}
}
]
}
Error/Warning:
"Matches multiple schemas when only one must validate."
The problem is that your X property in objectA and Y property en objectB are not required, so an empty object, that is { }, validates against both.
Also, if you want an array with objectA and objectY to be valid, you need to use anyOf instead of oneOf.
{
"definitions": {
"objectA": {
"type": "object",
"properties": {
"X": {
"type": "string"
}
},
"required": ["X"]
},
"objectB": {
"type": "object",
"properties": {
"Y": {
"type": "string"
}
},
"required": ["Y"]
}
},
"oneOf":
[
{"$ref": "#/definitions/objectA"},
{"$ref": "#/definitions/objectB"},
{
"type": "array",
"minItems": 1,
"items":
{
"anyOf":
[
{"$ref": "#/definitions/objectA"},
{"$ref": "#/definitions/objectB"}
]
}
}
]
}
I added the minItems if you don't want an empty array to validate.
So I have a JSON schema with additionalProperties rule set to false like.
{
"type": "object",
"properties": {
"metadata": {
"type": "object",
"properties": {
"a": {
"type": "string"
},
"b": {
"type": "string"
},
"c": {
"type": "string"
}
}
},
"street_type": {
"type": "string",
"enum": [
"Street",
"Avenue",
"Boulevard"
]
}
},
"additionalProperties": false
}
and a payload like
{
"metadata": {
"a": "aa",
"b": "bb",
"c": "cc",
"d": "dd"
}
}
Should I expect my JSON schema parser/validator to pass the validation, the JSON schema parser I am using com.github.fge.jsonschema.main.JsonSchema passes validation though metadata/d is not present in the schema with additionalProperties set to false,
This is very misleading, can someone direct me in the correct direction.
Is the additionalProperties JSON schema definition only applies to top-level fields and not to any nested level fields?
Is the additionalProperties JSON schema definition only applies to top-level fields and not to any nested level fields?
No you should be able to put it at whichever level you need as long as it is in a schema describing an object. In your case you simply put it at the wrong place. This should work:
{
"type": "object",
"properties": {
"metadata": {
"type": "object",
"properties": {
"a": {
"type": "string"
},
"b": {
"type": "string"
},
"c": {
"type": "string"
}
},
"additionalProperties": false
},
"street_type": {
"type": "string",
"enum": [
"Street",
"Avenue",
"Boulevard"
]
}
}
}
Let say that you wanted to validate the following object as is:
{
a: {
b: {
c: {
d: 42
}
}
}
}
One valid schema for it would be:
{
"type": "object",
"additionalProperties": false,
"properties": {
"a": {
"type": "object",
"additionalProperties": false,
"properties": {
"b": {
"type": "object",
"additionalProperties": false,
"properties": {
"c": {
"type": "object",
"additionalProperties": false,
"properties": {
"d": {
"const": 42
}
}
}
}
}
}
}
}
}
The schema above is extremely verbose but is here for illustration purpose. You should be able to make it a bit more succinct by using $ref and combining schemas together.
I have a JSON schema I want to change by a dependency of one of the values of the JSON structure. For example if {"foo":1} also include {"fooBar":"number"} in the schema, resulting in {"foo":"number", "fooBar":"number"} but if {"foo":2} instead include {"fooBar2":"bool", "fooBar3":"string"} resulting in {"foo":1, "fooBar2":"bool", "fooBar3":"string"}. Is this possible.
I know how to make the inclusion of a key change the schema (code example from here) but I cannot find any example on how this could be done using values. If this is even possible.
{
"type": "object",
"properties": {
"name": { "type": "string" },
"credit_card": { "type": "number" },
"billing_address": { "type": "string" }
},
"required": ["name"],
"dependencies": {
"credit_card": ["billing_address"]
}
}
It can be done, but it's a little complicated. Below is the general pattern.
{
"type": "object",
"anyOf": [
{
"properties": {
"foo": { "enum": [1] },
"fooBar": { "type": "number" }
}
},
{
"properties": {
"foo": { "enum": [2] },
"fooBar2": { "type": "boolean" },
"fooBar3": { "type": "string" }
}
}
]
}