enter image description hereI have a json schema looks like below,and I want load the definitions into D and E based on the values of B and C for that I've written allOf conditioning.
and i'm using json-schema-validator for json schema validation in application.
i)the below schema always passing as valid because the allOf condition never evaluated and it's not
loading validators properties like maxLenth,multipleOf from the definitions.
ii)I was suspecting I did the conditioning in a wrong place(the root schema or sub schema) and i tried
moving this allof logic to subschema level(inside the B,C and D,E)
iii)I've tried executing the allOf example mentioned on https://json-schema.org/understanding-json-schema/reference/conditionals.html it is also passing as valid. for this I did verified on a online josn schema validator http://json-schema-validator.herokuapp.com/ which is also using same library json-schema-validator.
iv)is there any ValidationConfiguration requires for JsonSchemaFactory to validate the Draft7 jsonSchema conditioning since the Defaultlibrary is DRAFT-4 on this json-schema-validator.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": [
"A",
"B",
"C",
"D",
"E"
],
"properties": {
"A": {
"type": "string"
},
"B": {
"type": "string",
"enum": ["TEST1","TEST2"]
},
"C": {
"type": "string",
"enum": ["TEST3","TEST4"]
},
"D": {
"type": "object"
},
"E": {
"type": "object"
}
},
"allOf": [
{
"if": {
"properties": { "B": { "const": "TEST1" } }
},
"then": {
"properties": { "D": { "$ref": "#/definitions/test" } }
}
},
{
"if": {
"properties": { "B": { "const": "TEST2" } }
},
"then": {
"properties": { "D": { "$ref": "#/definitions/testTwo" } }
}
},
{
"if": {
"properties": { "C": { "const": "TEST3" } }
},
"then": {
"properties": { "E": { "$ref": "#/definitions/testThree" } }
}
},
{
"if": {
"properties": { "C": { "const": "TEST4" } }
},
"then": {
"properties": { "E": { "$ref": "#/definitions/test4" } }
}
}
],
"definitions": {
"testOne":{"type":"object"},
"testTwo":{"type":"object"},
"testThree":{"type":"object"},
"testFour":{"type":"object"}
}
}
And the javaCode looks like
#PostMapping("/sendMessage")
public ProcessingReport sendMessage(#RequestBody SampleRequest request) throws IOException, ProcessingException {
//step-1 writing request object into String
String requestJson = objectMapper.writeValueAsString(request);
//Step-2 getting jsonNode for requested json
JsonNode dataNode = JsonLoader.fromString(requestJson);
//step -3 creating jsonSchema factory(default)
JsonSchemaFactory factory = JsonSchemaFactory.byDefault();
//validating requested jsonNode(dataNode) against SchemaNode(schema of request json,which is loaded from resources)
ProcessingReport report = factory.getJsonSchema(schemaNode).validate(dataNode);
//Processing report resulting the given json validation is successful or not
if(!report.isSuccess()) {
System.out.println(report);
}
return report;
}
json-schema-validator only supports draft-03 and draft-04. if/then/const were added in later drafts. Those keywords get ignored resulting in the no-op behavior you're experiencing.
You have two choices
pick a different implementation that supports draft-07
Use the Implication Pattern instead. It's a little more verbose, but the result is the same.
Related
I want to create a JSON schema (v2020-12) which can validate design tokens
The problem here is, that those tokens could be nested infinitively and each nested property could have any name.
The schema should validate that a property can have either one other property with unknown name or a design token. Other properties should not be allowed.
This should be valid:
{
"unknown_property_1": {
"unknown_property_2": {
<< nesting with unknown depth >>
"unknown_property_n": {
"$value": "#fff",
"$type": "this is a token with mandatory $value and $type"
}
}
}
}
This not:
{
"unknown_property_1": {
"fancy_unwanted_property": 123,
"unknown_property_2": {
"unknown_property_n": {
"$value": "#fff",
"$type": "this is a token with mandatory $value and $type"
}
}
}
}
I experimented with "anyOf" and "$ref" but couldnt get any close.
Recursive structures can be defined quite elegantly in JSON Schema. Here's the simplest version.
{
"type": "object",
"properties": {
"$value": { "type": "string" },
"$type": { "type": "string" }
},
"additionalProperties": { "$ref": "#" }
}
If you need a more strict version, this is an alternative.
{
"anyOf": [
{
"type": "object",
"properties": {
"$value": { "type": "string" },
"$type": { "type": "string" }
},
"additionalProperties": false
},
{
"type": "object",
"patternProperties": {
"": { "$ref": "#" }
}
}
]
}
I'm stuck on writing a correct json schema for my data.
Condition:
directory is an recursive object with optional property "$meta"
file can be string or object
if file is object it can have optional property "$meta" with oneOf "$data" or "$stringData" not both.
JSON Data:
{
"dir1": {
"dir1A": {},
"dir1B": {
"dir1B01": {
"dir1B0101": {},
"dir1B0102": {},
"dir1B0103": {},
"file1B.txt": {
"$meta": {},
"$data": "ooo",
"$stringData": "Netus et malesuada" // here should failed
}
}
}
},
"dir2": {
"file2.txt": "dolor sit amet" // here should OK but failed
},
"file1.txt": "Lorem Ipsum"
}
Schema:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/dir.schema.json",
"type": "object",
"patternProperties": {
"^[^\\/?%*:|\"<>]+$": { // failed if name consist of invalid chars
"$ref": "#/$defs/directory"
}
},
"additionalProperties": false,
"$defs": {
"file": {
"oneOf": [
{
"type": "string"
},
{
"type": "object",
"properties": {
"$data": {
"type": "string",
"description": "base64 encoded data"
},
"$stringData": {
"type": "string"
}
},
"oneOf": [
{
"not": {
"anyOf": [
{
"required": [
"$data",
"$stringData"
]
}
]
}
},
{
"allOf": [
{
"not": {
"required": [
"$data"
]
}
},
{
"not": {
"required": [
"$stringData"
]
}
}
]
}
]
}
]
},
"directory": {
"anyOf": [
{
"$ref": "#/$defs/file"
},
{
"type": "object",
"properties": {
"$meta": {
"type": "object",
"additionalProperties": true,
"properties": {
"createdAt": {
"type": "string"
},
"size": {
"type": "integer"
},
}
}
},
"patternProperties": {
"^[^\\/?%*:|\"<>]+$": {
"$ref": "#"
}
}
}
]
}
}
}
Live Demo: https://www.jsonschemavalidator.net/s/HNFtNfRw
Thank you for help
There are a few issues, so let's walk through deriving this schema one step at a time.
Let's start with the object representation of a file. Here's the easy part.
{
"type": "object",
"properties": {
"$meta": { "$ref": "#/$defs/meta" },
"$data": { "type": "string" },
"$stringData": { "type": "string" }
}
}
Then we need to add the assertion that one and only one of $data or $stringData is required. The oneOf keyword can express this cleanly.
"oneOf": [
{ "required": ["$data"] },
{ "required": ["$stringData"] }
]
Only one of the schemas can validate to true. If both properties are present, the schema will fail.
Next, let's define a file can be represented as a string or an object.
{
"anyOf": [
{ "type": "string" },
{ "$ref": "#/$defs/object-file" }
]
}
Notice that we are using anyOf instead of oneOf in this case. anyOf passes validation as soon as one of the schemas is valid. If the first one passes validation, it doesn't need to check the second one. If oneOf is used, the validator needs to check both schemas because oneOf requires that only one of the schemas passes validation and the rest fail. Because one of these schemas is a string and the other is an object, it's impossible for any JSON data to be both a string an object. Therefore, using oneOf only makes the validation less efficient.
That covers files, let's do directories next.
{
"type": "object",
"properties": {
"$meta": { "$ref": "#/$defs/meta" }
},
"patternProperties": {
"^[^\\/?%*:|\"<>]+$": { "$ref": "#/$defs/file-or-directory" }
},
"additionalProperties": false
}
The recursive reference part is pretty simple, but we have a problem with the $meta property. $meta matches the regular expression in patternProperties, so $meta will need to validate against #/$defs/meta and #/$defs/file-or-directory, which is not what you want. What you need is something like an additionalPatternProperties keyword, but since that doesn't exist, you need to change your patternProperties regex to exclude $meta. Something like this, "^[^\\/?%*:|\"<>]+(?<!\\$meta)$".
Now all that's left is a schema to express that something is a file or a directory.
"anyOf": [
{ "$ref": "#/$defs/file" },
{ "$ref": "#/$defs/directory" }
]
Again, we want to use anyOf, but there's a bit more to this than you might notice at first glance. The problem is that something that matches the file schema will also match the directory schema. So, we can't use oneOf and the file schema needs to come first. If the directory schema was first, it would incorrectly validate files as directories. When the file schema comes first, it checks if it's a file first and the false positive that we would get from the directory schema becomes irrelevant.
I'll leave it to you to put the pieces together into a full schema, but leave a comment if you have any issues.
I've got a Pet object that could be either a dog or a cat
Depending on what noise they make I'd like to then be able to validate other fields.
schema:
{
"$id": "http://example.com",
"definitions": {
"pet": {
"type": "object",
"properties": {
"noise": {
"enum": [
"bark",
"meow"
]
}
}
},
"dog": {
"$ref": "#/definitions/pet",
"properties": {
"noise": {
"const": "bark"
},
"tail": {
"enum": [
"short",
"long"
]
}
}
},
"cat": {
"$ref": "#/definitions/pet",
"properties": {
"noise": {
"const": "meow"
},
"tail": {
"enum": [
"wavy",
"slinky"
]
}
}
}
},
"type": "object",
"properties": {
"pets": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/dog",
"$ref": "#/definitions/cat"
}
]
}
}
}
}
This works when running the following json through:
{"pets":[{"noise":"meow","tail":"wavy"}]}
but not when running:
{"pets":[{"noise":"bark","tail":"long"}]}
[$.pets[0].tail: does not have a value in the enumeration [wavy, slinky], $.pets[0].noise: must be a constant value meow]
or
{"pets":[{"noise":"bark","tail":"long"},{"noise":"meow","tail":"wavy"}]}
[$.pets[0].tail: does not have a value in the enumeration [wavy, slinky], $.pets[0].noise: must be a constant value meow]
I can get this working by using if/else in the json schema, but requires another type to avoid a circular dependency:
"petWithConstraints": {
"$ref":"#/definitions/pet",
"allOf": [
{
"if": {
"properties": {
"noise": {
"const": "bark"
}
}
},
"then": {
"$ref": "#/definitions/dog"
}
},
{
"if": {
"properties": {
"noise": {
"const": "meow"
}
}
},
"then": {
"$ref": "#/definitions/cat"
}
}
]
}
}
This means for every new definition it also requires another if statement.
Is there a better method of doing this? (without the extra definition/if statement)
For those that come across this, this was a syntactical error.
Each ref should have been in it's own code block.
The corrected part of the schema looks like the following:
"properties": {
"pets": {
"type": "array",
"items": {
"anyOf": [
{ // Notice each $ref is encapsulated in it's own block
"$ref": "#/definitions/cat"
},
{
"$ref": "#/definitions/dog"
}
]
}
}
}
Running the following json through gave expected results
{"pets":[{"noise":"bark","tail":"long"},{"noise":"meow","tail":"wavy"}]}
[]
{"pets":[{"noise":"bark","tail":"long"},{"noise":"meow","tail":"wavy"},{"noise":"meow","tail":"slinky"},{"noise":"bark","tail":"short"}]}
[]
{"pets":[{"noise":"bark","tail":"long"},{"noise":"meow","tail":"wavy"},{"noise":"meow","tail":"slinky"},{"noise":"bark","tail":"short"},{"noise":"meow","tail":"short"}]}
[$.pets[4]: should be valid to one and only one of the schemas ]
I have a json object like:
{
"session": {
"session_id": "A",
"start_timestamp": 1535619633301
},
"sdk": {
"name": "android",
"version": "21"
}
}
The sdk name can either be android or ios. And the session_id is based on name field in sdk json. I have written a json schema using conditional statement (Using draft 7) as follows:
But it works in an unexpected manner:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$ref": "#/definitions/Base",
"definitions": {
"Base": {
"type": "object",
"additionalProperties": false,
"properties": {
"session": {
"$ref": "#/definitions/Session"
},
"sdk": {
"$ref": "#/definitions/SDK"
}
},
"title": "Base"
},
"Session": {
"type": "object",
"additionalProperties": false,
"properties": {
"start_timestamp": {
"type": "integer",
"minimum": 0
},
"session_id": {
"type": "string",
"if": {
"SDK": {
"properties": {
"name": {
"enum": "ios"
}
}
}
},
"then": {
"pattern": "A"
},
"else": {
"pattern": "B"
}
}
},
"required": [
"session_id",
"start_timestamp"
],
"title": "Session"
},
"SDK": {
"type": "object",
"additionalProperties": false,
"properties": {
"version": {
"type": "string"
},
"name": {
"type": "string",
"enum": [
"ios",
"android"
]
}
},
"required": [
"name",
"version"
],
"title": "SDK"
}
}
}
So the following JSON Passes:
{
"session": {
"session_id": "A",
"start_timestamp": 1535619633301
},
"sdk": {
"name": "ios",
"version": "21"
}
}
But this fails:
{
"session": {
"session_id": "B",
"start_timestamp": 1535619633301
},
"sdk": {
"name": "android",
"version": "21"
}
}
can someone explain y?.. Even this passes:
{
"session": {
"session_id": "A",
"start_timestamp": 1535619633301
},
"sdk": {
"name": "android",
"version": "21"
}
}
I think you're having a similar problem as in this question.
#Relequestual is right in that you need the properties keyword around your SDK callout. But for what you want to do, you need to reorganize.
Subschemas only operate on their level in the instance, not at the root.
Consider this schema for a simple JSON object instance containing a one and a two property:
{
"properties": {
"one": {
"enum": ["yes", "no", "maybe"]
},
"two": {
"if": {
"properties": {
"one": {"const": "yes"}
}
},
"then": {
... // do some assertions on the two property here
},
"else": {
...
}
}
}
}
The if keyword under the two property can only consider the portion of the instance under the two property (i.e. two's value). It's not looking at the root of the instance, so it can't see the one property at all.
To make it so that the subschema under the two property subschema can see the one property in the instance, you have to move the if outside of the properties keyword.
{
"if": {
"properties": {
"one": {"const" : "yes"}
}
},
"then": {
... // do some assertions on the two property here
},
"else": {
... // assert two here, or have another if/then/else structure to test the one property some more
}
}
For two possible values of one, this is pretty good. Even three possible values isn't bad. However, as the possible values of one increases, so does the nesting of ifs, which can make your schema horrible to read (and possibly make validation slower).
Instead of using the if/then/else construct, I suggest using an anyOf or oneOf where each subschema represents a valid state for the instance, given the varying values of one.
{
"oneOf": [
{
"properties": {
"one": {"const": "yes"},
"two": ... // do some assertions on the two property here
}
},
{
"properties": {
"one": {"const": "no"},
"two": ... // do some assertions on the two property here
}
},
{
"properties": {
"one": {"const": "maybe"},
"two": ... // do some assertions on the two property here
}
}
]
}
This is much cleaner in my opinion.
Hopefully that explanation helps you reconstruct your schema to allow those other instances to pass.
You have to move your conditional to a high enough level to be able to reference all of the the properties it needs to reference. In this case, that's the /definitions/Base schema. Then you just need to write your schemas properly as Relequestual explained.
{
"$ref": "#/definitions/Base",
"definitions": {
"Base": {
"type": "object",
"properties": {
"session": { "$ref": "#/definitions/Session" },
"sdk": { "$ref": "#/definitions/SDK" }
},
"allOf": [
{
"if": {
"properties": {
"sdk": {
"properties": {
"name": { "const": "ios" }
}
}
},
"required": ["sdk"]
},
"then": {
"properties": {
"session": {
"properties": {
"session_id": { "pattern": "A" }
}
}
}
},
"else": {
"properties": {
"session": {
"properties": {
"session_id": { "pattern": "B" }
}
}
}
}
}
]
},
...
}
The value of if must be a JSON Schema. If you were to take lines https://gist.github.com/Relequestual/f225c34f6becba09a2bcaa66205f47f3#file-schema-json-L29-L35 (29-35) and use that as a JSON Schema by itself, you would impose no validation constraints, because there are no JSON Schema key words at the top level of the object.
{
"SDK": {
"properties": {
"name": {
"enum": "ios"
}
}
}
}
This is allowed in the specification, because people may want to extend the functionality of JSON Schema by adding their own key words. So it's "valid" JSON Schema, but doesn't actually DO anything.
You Need to add properties to the schema for it to make sense.
{
"properties": {
"SDK": {
"properties": {
"name": {
"const": "ios"
}
}
}
}
}
Additionally, enum must be an array. When you only have a single item, you may use const.
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.