How to define an object with a veriable name in JSON Schema? - json

I want to create a JSON schema for this JSON "pseudo-code" example:
{
"xyz": {
"$something": {
"property_a": "...",
"property_b": "...",
"property_c": "..."
}
}
}
$something can be one of the following strings: foo, bar, or buz. My current schema looks like this:
{
"xyz": {
"id": "xyz",
"type": "object",
"properties": {
"foo": {
"id": "foo",
"type": "object",
"additionalProperties": false,
"required": ["property_a"],
"properties": {
"property_a": {
"id": "property_a",
"type": "string"
},
"property_b": {
"id": "property_b",
"type": "string"
},
"property_c": {
"id": "property_a",
"type": "string"
}
}
},
"bar": {
... copy&paste foo
},
"buz": {
... copy&paste foo
}
}
}
}
It's working, but it's a lot duplicated code. So I'm looking for a more elegant way for implementing it.
How to define a list of values (lie enum) allowed as name for a property in JSON Schema?

patternProperties works like properties, except the keys of the object are regular expressions.
https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-validation-01#section-6.5.5
An example from the Understanding JSON Schema site
{
"type": "object",
"patternProperties": {
"^S_": { "type": "string" },
"^I_": { "type": "integer" }
},
"additionalProperties": false
}
In this example, any additional properties whose names start with the
prefix S_ must be strings, and any with the prefix I_ must be
integers. Any properties explicitly defined in the properties keyword
are also accepted, and any additional properties that do not match
either regular expression are forbidden.

Related

UUIDs as object keys in JSON Schema

I am trying to define a JSON Schema for a JSON API that uses UUIDs as their key for a JSON object. What makes it more complex is that it is also a nested object.
Example:
{
"nodes": {
"7059e5ad-fac0-4fda-aa3e-2655d6e60506": {
"type": "Class",
"name": "Supermarket",
"data": {},
"instances": {},
}
}
}
Which has a generated schema like this:
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"nodes": {
"type": "object",
"properties": {
"7059e5ad-fac0-4fda-aa3e-2655d6e60506": {
"type": "object",
"properties": {
"type": {
"type": "string"
},
"name": {
"type": "string"
},
"data": {
"type": "object"
},
"instances": {
"type": "object"
}
}
}
}
}
}
}
Is there a way I can make a schema where there are no UUIDs values in the schema because these values can be anything?
You can leverage the existing uuid format definition, in draft2019-09 and later:
"propertyNames": {
"format": "uuid"
},
"additionalProperties": {
.. definition of the values that go under the uuid properties ..
}
Replace properties with patternProperties, and your UUID with the following regular expression.
^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$
The patternProperties keyword is like the properties keyword, but you can use a regular expression for the key. This works in draft-04 JSON Schema, but it's highly recommended to use a newer version of JSON Schema if you can.
This regex was borrowed, but I'm reasonably confident it is for a UUID.

How to create JSON schema for validate if given field of type string contains value from given string array

I need to create JSON Schema (can use any version) to validate the string field that may only contain values from given string array in other field.
MVE Example:
For the "picked" the only valid values are ones specified in "values"
Valid:
{
"values": ["Foo", "Bar", "Baz"],
"picked": "Bar"
}
Invalid:
{
"values": ["Foo", "Bar", "Baz"],
"picked": "NotFromValues"
}
Schema:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"values": {
"type": "array",
"items": { "type": "string" }
},
"picked": {
"type": "string"
// How can I validate picked?
}
}
}
What you're wanting isn't supported by json schema out of the box. You'll need some custom logic to do it.
There has been a lot of discussion on the spec repo about it. If you do a search for issues with the $data label, you'll have a lot to read. The short of it is that no one could agree on how it should work. The topic is now all but abandoned.
To address this, I have taken advantage of the 2019-09 (and later) vocabularies feature. I have written a new vocabulary that defines a data keyword to support this kind of behavior. The catch is it's only supported (to my knowledge) in my implementation, JsonSchema.Net, but you could implement it yourself if you're using an implementation that lets you define your own keywords.
For your example, you'll need this:
{
"$schema": "https://gregsdennis.github.io/json-everything/meta/data",
"type": "object",
"properties": {
"values": {
"type": "array",
"items": { "type": "string" }
},
"picked": {
"type": "string",
"data": {
"enum": "#/values"
}
}
}
}
(Note that the $schema value changed.)
This will find the values property at the root of your instance (an array), replace "/values" with that, then evaluate as if the value of data was a schema.
In the end, for your example, you're evaluating against this schema:
{
"$schema": "https://gregsdennis.github.io/json-everything/meta/data",
"type": "object",
"properties": {
"values": {
"type": "array",
"items": { "type": "string" }
},
"picked": {
"type": "string",
"enum": [ "Foo", "Bar", "Baz" ]
}
}
}
but the value of enum comes from the instance.
You can test this at https://json-everything.net/json-schema.

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.

Use object property keys as enum in JSON schema

I'm trying to validate a JSON file using JSON Schema, in order to find cases of "broken references". Essentially my file consists of items and groups, with each item belonging to a single group referenced by the groups property key, like so:
{
"items": {
"banana": {
"name": "Banana",
"group": "fruits"
},
"apple": {
"name": "Apple",
"group": "fruits"
},
"carrot": {
"name": "Carrot",
"group": "vegetables"
},
"potato": {
"name": "Potato",
"group": "vegetables"
},
"cheese": {
"name": "Cheese",
"group": "dairy"
}
},
"groups": {
"fruits": {
"name": "Fruits"
},
"vegetables": {
"name": "Vegetables"
}
}
}
In the example above the item cheese is to be considered invalid, as there are no dairy property in the groups object. I've tried to validate this using the following schema:
{
"$schema": "http://json-schema.org/draft-06/schema#",
"title": "Food",
"id": "food",
"type": "object",
"properties": {
"items": {
"type": "object",
"patternProperties": {
"^[A-Za-z0-9-_.:=]+$": {
"properties": {
"name": {
"type": "string",
"pattern": "^[A-Za-z- ]+$"
},
"group": {
"pattern": "^[a-z]+$",
"enum": {
"$data": "/groups"
}
}
}
}
}
},
"groups": {
"type": "object",
"patternProperties": {
"^[A-Za-z0-9-_]+$": {
"properties": {
"name": {
"type": "string",
"pattern": "^[A-Za-z- ]+$"
}
}
}
}
}
},
"additionalProperties": false
}
This has the effect that the enum for group is populated by the property values in groups, but what I want to do is use the property keys defined in groups.
If I add a property like e.g. groupIds and let that be an array of all property keys found in groups and specify the enum as "$data": "/groupIds" it does work, so I take this to be a JSON pointer issue.
The enum keyword in JSON Schema is defined as:
The value of this keyword MUST be an array. This array SHOULD have at least one element. Elements in the array SHOULD be unique.
So if I could only get JSON pointer to reference an object's keys rather than its values I guess the enum validation would just work. I'm thinking something like "$data": "/groups/.keys", "$data": "/groups/$keys" or similar, but haven't found it while googling or reading the spec. Is there such a thing or has it ever been proposed?
There is no such thing. It’s very close to general expressions inside JSON and it may have some use cases, but there is no such specification.

Appropriate behavior of JSON schema for nested required properties?

Say I have a JSON schema like this:
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"name": {"type": "string"},
"myKey": {"$ref": "myKey.json#"}
},
"additionalProperties": false
}
and then somewhere else I have myKey.json:
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object"
"properties": {
"A": {
"type": "array",
"description": "Array of stream object IDs",
"items": { "type": "integer" }
},
"B": {
"type": "array",
"description": "Array of stream object IDs",
"items": {"type": "integer" }
}
},
"required": ["A", "B"],
"additionalProperties": false
}
The important thing here is that inside myKey, there are two required properties, but myKey itself is not required. Does the
fact that myKey have required properties propagate up to the top so that myKey is forced to become required?
In other words, which of these two objects, if any, ought to be validated by this schema?
{
"name": "myName",
}
{
"name": "myOtherName",
"myKey":
{
"A": [1, 2] // Note that B is missing
}
}
The first one is valid according to the schema and the second one no.
The way to read properties tag is: if this property key is found, then it must satisfy this schema.
{
"name": "myName"
}
For the object above, myKey is not required so it satisfies the schema.
{
"name": "myOtherName",
"myKey":
{
"A": [1, 2] // Note that B is missing
}
}
For the second object, myKey is present, so it must satisfy the schema of that property. But it is not satisfied because it should have both A and B properties.
The same idea is applied to every level. The following object satisfies the schema:
{
"name": "myOtherName",
"myKey":
{
"A": [],
"B": []
}
}
But this does not:
{
"name": "myOtherName",
"myKey":
{
"A": [],
"B": ""
}
}