How to use dependentRequired with relative reference in JSON schema - json

I have below JSON schema in my app, and i am using NewtonSoft JSON schema validation library to validate user JSON against my schema.
The rules that i need to set are -
if user sets property2 in the JSON then property3 should also exist and subProperty2 should also exist underneath property3.
if user does not set property2 then property3 is not required.
I used dependentRequired for this with relative reference using period(.) but that did not work with NewtonSoft package. I tried in 2 different ways, both without expected result.
{
"type": "object",
"additionalProperties": false,
"properties": {
"property1": {
...
},
"property2": {
...
},
"property3": {
"type": "object",
"additionalProperties": false,
"properties": {
"subProperty1": {
...
},
"subProperty2": {
...
},
}
}
},
"required": [
"property1"
]
}
//try 1
"dependentRequired": {
"property2": [ "property3.subProperty2" ]
},
//try 2
"dependentRequired": {
"property2": {
"required": [ "property3" ],
"property3": {
"required": [ "subProperty2" ]
}
}
}
Can someone help me with this?

The dot notation used in the first try is not supported in JSON Schema.
The second try is the right idea, but since you are using a schema, you need to use dependentSchemas instead of dependentRequired. Also, you are missing the properties keyword to make that a valid schema.
"dependentSchemas": {
"property2": {
"required": [ "property3" ],
"properties": {
"property3": {
"required": [ "subProperty2" ]
}
}
}
},

Related

How to write complex conditional validation in JSON schema?

Below is the sample JSON schema.
I need to implement this validation -
If property1 has a value of "abc" and property2 has "abc111" in the list of provided values, then subProperty1 is mandatory inside property3
{
"type": "object",
"additionalProperties": false,
"properties": {
"property1": {
"type": "string"
},
"property2": {
"type": "array",
"items": {
"type": "string"
}
},
"property3": {
"type": "object",
"properties": {
"subProperty1": {
...
},
"subProperty2": {
...
},
}
}
}
}
I tried to implement it using this JSON -
"if": {
"properties": {
"property1": {
"const": "abc"
},
"property2": {
"contains": {
"properties": {
"items": {
"const": "abc111"
}
}
}
}
}
},
"then": {
"dependentSchemas": {
"property1": {
"required": [ "property3" ],
"message": "Error - Missing property3 with subProperty1",
"properties": {
"property3": {
"required": [ "subProperty1" ]
}
}
}
}
}
But this doesnt work as expected. Could someone please help me fix the conditional logic?
It's hard to say without knowing in what way things aren't working as expected. But you may be missing a required keyword in your if clause -- in order to ensure that property1 really is present. (The properties keyword evaluates to true if the property isn't present at all.) Then you would also need a required: ["property3"] in your then clause.
Also, you don't need dependentSchemas at all -- you are adding a dependency on property1, but that's already in the if clause so it's redundant.

Cannot validate recursive schema

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.

How to create JSON schema for dynamic nested map of objects

I am trying to create JSON schema for below JSON,
{
"messages":{
"bookCreated":{
"default":{
"channels":{
"sqs":{
"enabled":true,
"topic":"sample-topic",
"shares":true
}
}
}
},
"bookCreationInProgress":{
"default":{
"channels":{
"sns":{
"enabled":true,
"topic":"sample-sns",
"shares":true
}
}
}
},
"bookCreationCompleted":{
"default":{
"channels":{
"s3":{
"enabled":true,
"topic":"sample-s3-bucket",
"shares":true
}
}
}
}
}
}
Inside message bookCreated, bookCreationInProgress , bookCreationCompleted similarly we have several dynamic properties. Inside each of these objects default and channel details are mandatory.
And each channel has a set of mandatory attributes.
I browsed internet to create JSON schema for the above json but I couldn't get any reference of how to create json schema for nested map objects.
Since I couldn't able to construct the json schema for very first dynamic object I couldn't able to construct the schema further.
{
"$schema": "app_messages",
"type": "object",
"additionalProperties": true,
"anyOf": [
{
"required": ["messages"]
}
],
"properties": {
"id": {
"type": "string"
}
}
}
It would be really great if somebody would help me to share the pointers of how to handle map of dynamic properties in JSON schema. Any help would be really appreciable.
A good solution use "additionalProperties" as json-object (instead of boolean) combined to "$ref" and "$defs".
A talking example might be:
{
"$schema": "app_messages",
"type": "object",
"additionalProperties": {
"anyOf": [
{ "$ref": "#/$defs/subobj1" },
{ "$ref": "#/$defs/subobj2" }
]
},
"properties": {
"id": { "type": "string" }
},
"$defs": {
"subobj1": {
"type": "object",
"properties": {
"message": { "type": "string" }
...
},
"required": [ "message" ]
},
"subobj2": {
"type": "object",
"properties": {
"message": { "type": "string" }
...
},
"required": [ "message" ]
}
}
}
In this way main object can have "id" properties and all additional properties must match one of sub definition "subobj1" or "subobj2".

JSON Schema Dependency Between Properties in Separate Objects

Creating a schema for the following document:
{
"name": "billy",
"foo": {
"this": "something"
},
"bar": {
"that": "something"
}
}
I want the schema to require $.bar.that if $.foo.this is present.
Tried a dependency on this with a JSON pointer to that.
Tried an if/then block, but I couldn't figure out how to say "this is present" in the if portion of the block.
dependencies only works with the current object, so it has to be done with if/then. You need to use properties to set required at every level.
{
"if": {
"properties": {
"foo": { "required": ["this"] }
},
"required": ["foo"]
},
"then": {
"properties": {
"bar": { "required": ["this"] }
},
"required": ["bar"]
}
}

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.