Cannot validate recursive schema - json

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.

Related

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

Conditionally apply sub-schema, based on another file's property

Does json-schema allow to have conditional sub-schema based on the property value from another file?
Example:
Jsons:
another.json
{
"honey": "honey"
}
main.json
{
"hello": "abc"
}
Schema
{
"type": "object",
"properties": {
"hello": {
"type": "string"
}
},
"required": ["hello"],
"if": {
"properties": {
"another.json#/honey": { -->> is this possible
"const": "honey"
}
}
},
"then": {
"properties": {
"hi": {
"type": "string"
}
},
"required": ["hi"]
}
}
The main.json should fail validation screaming "hi" is required since, another.json has "honey" as value.
Now, changing another.json to
{
"honey": "not_honey"
}
Should pass the validation for main.json
The question is
No. JSON Schema has no notion of files, only "a JSON instance", of which only one can be validated at a time. You would need to combine them yourself in some way.

JSON schema to validate that property name matches nested value

We are using JSON to store some configuration settings. For example:
{
"source1": {
"name": "source1",
"standalone": false
},
"source2": {
"name": "source2",
"standalone": true
},
"source3": {
"name": "source3",
"standalone": true
}
}
As you can see, the source names are variable and are repeated for convenience inside the object under a property name.
We're currently validating this using a JSON schema as follows:
{
"$schema": "http://json-schema.org/draft-06/schema#",
"type": "object",
"patternProperties": {
"^\\w[-\\w_]*$": { "$ref": "#/definitions/source" }
},
"additionalProperties": false,
"definitions": {
"source": {
"type": "object",
"properties": {
"name": { "type": "string" },
"standalone": { "type": "boolean" }
},
"required": ["name", "standalone"],
"additionalProperties": false
}
}
}
Is there a way to require that the property name matches the value using JSON schema? In other words, is there a way to make sure the following example fails to validate?
{
"a": {
"name": "b",
"standalone": false
}
}

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!

A common JSON Schema for similar structure

I'm completely new to json and json schema, so I have a question (yet I don't know how much it make sense). Can we create a json schema which is common for similar type of structure. For example:
One single schema can be used to validate following json
JSON:
{
"Team_Table":
[{"Name":"New Zealand", "Match":"Six", "Won":"Six"}]
}
And
{
"Story_Taller":
[{"Story":"No Name", "Chapter":"Don't know"}]
}
Similarities:
Both have only one object in the array
Objects have string value.
Dissimilarities:
Number of properties are different
Keys are different in both
Can we do this?
Maybe this helps you along:
{
"properties": {
"Story_Taller": {
"type": "array",
"maxItems": 1,
"items": {
"properties": {
"Chapter": {
"type": "string"
},
"Story": {
"type": "string"
}
},
"additionalProperties": false
}
},
"Team_Table": {
"type": "array",
"maxItems": 1,
"items": {
"properties": {
"Name": {
"type": "string"
},
"Match": {
"type": "string"
},
"Won": {
"type": "string"
}
},
"additionalProperties": false
}
}
},
"oneOf": [
{
"title": "Story_Taller",
"required": [
"Story_Taller"
]
},
{
"title": "Team_Table",
"required": [
"Team_Table"
]
}
]
}
in (short) words:
in your JSON there must be one property of either "Story_Taller" or "Team_Table" with a maximum of 1 item
"oneOf": [ ... ]
Properties of both arrays are defined by items
"Story_Taller" must have "Chapter" and "Story" and no additional properties.
"Team_Table" must have "Name", "Match", "Won" and no additional properties.
And all of them are defined as strings.