How do I inherit from schemas that are in the same file? - json

Given the following schema fragment
{
"$schema": "http://json-schema.org/schema#",
"definitions": {
"uuid": {
"title": "uuid",
"description": "UUID ( http://regex101.com/r/eJ7gN2 )",
"type": "string",
"minLength": 36,
"maxLength": 36,
"pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
},
"reference": {
"title": "reference",
"description": "A reference to an object by UUID",
"type": "object",
"properties" : {
"id": {
"$ref": "#/definitions/uuid"
}
},
"required": [ "id" ]
},
"imageref": {
"title": "imageref",
"description": "Reference to an image using an internal UUID",
"type": "object",
"properties": {
"size": {
"description": "Largest side of the image, depends on aspect ratio",
"type": "integer",
"minimum": 0,
"maximum": 1600
},
"crop": {
"description": "Flag to tell the system to crop the image or not",
"type": "boolean",
"default": false
}
}
}
}
}
How do I specify that imageref should inherit from reference, so that imageref has the id property that is in reference?
I have lots of other things that will be reference types and I am trying to eliminate duplicating that definition over and over again.

To inherit from another schema, you use allOf combined with $ref:
{
"title": "Extension schema",
"allOf": [{"$ref": "/path/to/base/schema"}]
}
Inheriting from another schema within the same file is just the same - you reference it using fragments, just like you have elsewhere:
{
"title": "Extension schema",
"allOf": [{"$ref": "#/definitions/reference"}]
}

Related

How to check for matching properties using a JSON schema?

I'm making a Discord bot in Python that reacts to certain keywords in messages and my script uses a JSON file that might for example look like this:
{
"characters": {
"john": {
"name": "Johnny",
"hex_colour": "0xC61B1B"
},
"marc": {
"name": "Marcus",
"hex_colour": "0x8AC0FF"
}
},
"reactions": [
{
"keywords": ["Hi", "Hello"],
"quotes": {
"john": ["Hello my name is Johnny"]
"marc": [
"Hi there. I'm Marcus.",
"Not now, I'm looking for John, have you seen him?"
]
}
},
{
"keywords": ["Bye"],
"quotes": {
"john": ["See you later!"]
}
}
]
}
As you can see the structure is quite complex, so I decide to make a schema for it so vscode could point out any issues. It currently looks like this:
{
"title": "Quotes data",
"description": "Quotes bot data file",
"type": "object",
"properties": {
"characters": {
"title": "Character collection",
"description": "A collection of the available characters.",
"type": "object",
"additionalProperties":{
"title": "Character tag",
"description": "A tag that represents the character as a short string",
"type": "object",
"properties": {
"name": {
"title": "Character name",
"description": "The name of the character.",
"type": "string"
},
"hex_colour": {
"title": "Character colour",
"description": "The hex code of the colour in 0x000000 format.",
"type": "string"
},
"image_url": {
"title": "Character image",
"description": "An url to an image of the character.",
"type": "string"
}
},
"required": ["name"],
"additionalProperties": false
}
},
"reactions": {
"title": "Reaction collection",
"description": "A list of the reactions",
"type": "array",
"items": {
"type": "object",
"properties": {
"keywords": {
"title": "Keyword list",
"description": "A list of keywords for the bot to react to.",
"type": "array",
"items": {
"title": "Keyword",
"type": "string",
"minItems": 1,
"uniqueItems": true
}
},
"quotes": {
"title": "Quotes collection",
"description": "A collection of characters with quotes.",
"type": "object",
"additionalProperties":{
"title":"Character tag",
"description": "A tag that matches a character above.",
"type": "array",
"items": {
"title": "Quote",
"description": "A quote to react with.",
"type": "string",
"minItems": 1,
"uniqueItems": true
}
}
}
},
"required": ["keywords", "quotes"],
"additionalProperties": false
}
}
},
"required": ["characters", "reactions"],
"additionalProperties": true,
"allowTrailingCommas": true
}
So for the properties of the "characters" object, any property name is allowed, therefore I use "additionalProperties". Later on, for the "quotes" object, I did the same. But in this case, not any property should be allowed. Only those that match one of the properties of the "characters" object is allowed. Is there any way to make the schema check for matching properties?
This is a very specific semantic check and not possible using the standard specification of JSON Schema. Validation can't access an arbitrary set of property names and also can't look up the validation path. You would need to add this as application code.

Erroneous successful validation by JSON-schema

The fields in nodes depend on the value of entity. That is, if entity = "pd", then nodes has some fields, while entity = " top " - nodes has completely different fields, despite the fact that they are strictly required. For some reason, the JSON string is accepted by the valid schema, even if there are no fields defined in nodes as required. I already entire head broke, where can be mistake in the most scheme?
JSON-schema:
{
"definitions": {},
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "http://example.com/root.json",
"type": "object",
"title": "The Root Schema",
"required": [
"virtual"
],
"properties": {
"virtual": {
"$id": "#/properties/virtual",
"type": "array",
"title": "The Virtual Schema",
"items": {
"$id": "#/properties/virtual/items",
"type": "object",
"title": "The Items Schema",
"required": [
"type",
"path",
"entity",
"nodes"
],
"properties": {
"type": {
"$id": "#/properties/virtual/items/properties/type",
"type": "string",
"title": "The Type Schema",
"default": "",
"examples": [
"bus"
],
"pattern": "^(.*)$"
},
"path": {
"$id": "#/properties/virtual/items/properties/path",
"type": "string",
"title": "The Path Schema",
"default": "",
"examples": [
"VBUS2"
],
"pattern": "^(.*)$"
},
"entity": {
"$id": "#/properties/virtual/items/properties/entity",
"type": "string",
"title": "The Entity Schema",
"default": "",
"examples": [
"topaz"
],
"enum": ["pde", "topaz"],
"pattern": "^(.*)$"
},
"nodes": {
"$id": "#/properties/virtual/items/properties/nodes",
"type": "array",
"title": "The Nodes Schema",
"items": {
"$id": "#/properties/virtual/items/properties/nodes/items",
"type": "object",
"title": "The Items Schema"
}
}
}
}
}
},
"anyOf": [
{
"if": {
"properties": { "virtual": { "properties": { "entity": { "const": "topaz" } } } }
},
"then": {
"properties": {
"virtual": {
"properties": {
"nodes": {
"items": {
"required": [
"uid",
"utype",
"uaddress",
"unozzles"
],
"properties": {
"uid": {
"$id": "#/properties/virtual/items/properties/nodes/items/properties/uid",
"type": "integer",
"title": "The Uid Schema",
"default": 0,
"examples": [
1
]
},
"utype": {
"$id": "#/properties/virtual/items/properties/nodes/items/properties/utype",
"type": "string",
"title": "The Utype Schema",
"default": "",
"examples": [
"dispenser"
],
"pattern": "^(.*)$"
},
"uaddress": {
"$id": "#/properties/virtual/items/properties/nodes/items/properties/uaddress",
"type": "string",
"title": "The Uaddress Schema",
"default": "",
"examples": [
"false"
],
"pattern": "^(.*)$"
},
"unozzles": {
"$id": "#/properties/virtual/items/properties/nodes/items/properties/unozzles",
"type": "boolean",
"title": "The Unozzles Schema",
"default": false,
"examples": [
false
]
}
}
}
}
}
}
}
}
},
{
"if": {
"properties": { "virtual": { "properties": { "entity": { "const" : "pde" } } } }
},
"then": {
"properties": {
"virtual": {
"properties": {
"nodes": {
"items": {
"required": [
"id",
"type",
"address",
"nozzles"
],
"properties": {
"id": {
"$id": "#/properties/virtual/items/properties/nodes/items/properties/id",
"type": "string",
"title": "The Id Schema",
"default": "",
"examples": [
"vrt_1"
],
"pattern": "^(.*)$"
},
"type": {
"$id": "#/properties/virtual/items/properties/nodes/items/properties/type",
"type": "string",
"title": "The Type Schema",
"default": "",
"examples": [
"dispenser"
],
"pattern": "^(.*)$"
},
"address": {
"$id": "#/properties/virtual/items/properties/nodes/items/properties/address",
"type": "integer",
"title": "The Address Schema",
"default": 0,
"examples": [
1
]
},
"nozzles": {
"$id": "#/properties/virtual/items/properties/nodes/items/properties/nozzles",
"type": "array",
"title": "The Nozzles Schema",
"items": {
"$id": "#/properties/virtual/items/properties/nodes/items/properties/nozzles/items",
"type": "integer",
"title": "The Items Schema",
"default": 0,
"examples": [
1,
2,
3
]
}
}
}
}
}
}
}
}
}
}
]
}
This JSON is valid:
{
"virtual": [
{
"type": "bus",
"path": "VUS1",
"entity": "pde",
"nodes": [
{
"id": "vrt_1",
"type": "string",
"address": 1,
"nozzles": [1, 2, 3]
},
{
"id": "vrt_2",
"type": "string",
"address": 2,
"nozzles": [1, 2, 3]
}
]
},
{
"type": "bus",
"path": "VUS2",
"entity": "topaz",
"nodes": [
{
"uid": 1,
"utype": "string",
"uaddress": "false",
"unozzles": false
},
{
"uid": "vrt_1",
"utype": "string",
"uaddress": "false",
"unozzles": false
}
]
}
]
}
And this JSON should not be applied, but is considered valid:
{
"virtual": [
{
"type": "bus",
"path": "VUS1",
"entity": "pde",
"nodes": [
{
"id_not_valid": "failure",
"type": 1,
"address": false,
"nozzles": [1, 2, 3]
},
{
"id": "vrt_2",
"type": "string",
"address": false,
"nozzles": [1, 2, 3]
}
]
},
{
"type": "bus",
"path": "VUS2",
"entity": "topaz",
"nodes": [
{
"uid_not_valid": "failure",
"utype": 1,
"uaddress": "false",
"unozzles": false
}
]
}
]
}
In theory, the second JSON should not be validated. For several reasons:
For entity= "pd", the required fields are "id", "type"," address "and"nozzles". In the second line of JSON instead the field "id" is replaced by the field "id_not_valid" - > the obligatory field " id " is absent and validation has to end in failure. The same for entity="top" - "the uid" is replaced by "id_not_valid"
For entity= "pd", the address field is of type token, in the second JSON line it is set to false, which corresponds to the type "boolean", but validation still takes place (the same if you assign an array or string value to address). For entity="top" type, the type is string, but the integer value 1 assigned to it is also assumed by the validator to be the correct string.
But the online validators on the links below say that everything is OK and both JSON conform to the scheme.
The first site
Second site
The third website
So I believe there is an error in the scheme.
The scheme itself was made by this example Example of JSON schema compilation
Any comments and tips on fixing JSON-schema, please
The schema is malformed.
(I'm ignoring the fact that the schema states entity should be "pde" or "topaz", but the instances have "pd" and "top". I assume this is a typo.)
Inside the anyOf, you have two items, each with an if conditional keyword. The schema presented by this keyword is
{
"properties": {
"virtual": {
"properties": {
"entity": {
"const": "topaz"
}
}
}
}
}
This is saying that if virtual has an entity property, then it should be "topaz". But the way that properties works is that it only fails validation if the instance is an object. However in #/properties, you declare that virtual should be an an array of objects where each item contains an entity property.
Since virtual is an array in your instance, none of the if condition keywords in the anyOf pass, so they defer to the else keywords for those subschemas, which don't exist (so the pass by default). This results in both subschemas for the anyOf passing.
I think what you're trying to do is validate each of the items inside the array based on the value of the entity property for that item. This means that you could have both a pde item and a topaz item in the array.
To do this you need to isolate where the variance is. In your case, it's the item level inside the virtual array. This is where you need to put your anyOf.
So you'll want to add your anyOf to #/properties/virtual/items. This is the only point in the schema where an if/then construct can key off of the entity property and enforce the nodes property.
Edit Things I would change
Remove all of the internal $id declarations. They only reiterate the location in the document and provide no additional functionality.
Remove the type and pattern declarations from entity. enum is sufficient here because it declares that the values must be one of the items in the array. Since these are both strings and match the given pattern, those keywords are redundant.
Move the anyOf alongside the properties keyword inside virtual and change it to a oneOf. This is the most specific location where you can access both the entities property and the nodes property. Changing it to a oneOf ensures that exactly one can be true.
Drop the if/then construct and just include the constant value in the then portion.
In the end, it would be structured something like this:
{
... ,
"properties": {
"virtual": {
"type": "array",
"title": "The Virtual Schema",
"items": {
"type": "object",
"title": "The Items Schema",
"required": [ "type", "path", "entity", "nodes" ],
"properties": {
"type": { ... },
"path": { ... },
"entity": {
"title": "The Entity Schema",
"default": "",
"examples": [
"topaz"
],
"enum": ["pde", "topaz"]
}
},
"oneOf": [
{
"properties": {
"entity": {"const": "topaz"},
"nodes": { ... }
}
},
{
"properties": {
"entity": {"const": "pde"},
"nodes": { ... }
}
}
]
}
}
}
}
Here, we're declaring that the items within the virtual array must be objects requiring 4 properties: type, path, entity, and nodes. We explicitly define type, path, entity using the properties keyword. But we conditionally define the nodes property using the oneOf and specifying a constant value for the entity property in each case.

JSON Schema Custom Definition

Can I have a custom specifier/attribute like "required" keyword to decorate my Json schema fields with custom specifications.
Like for example:
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Product",
"description": "A product from Acme's catalog",
"type": "object",
"properties": {
"id": {
"description": "The unique identifier for a product",
"type": "integer"
},
"name": {
"description": "Name of the product",
"type": "string"
},
"price": {
"type": "number",
"minimum": 0,
"exclusiveMinimum": true
},
"**credit-card**": {
"**type**": "**number**",
},
"tags": {
"type": "array",
"items": {
"type": "string"
},
"minItems": 1,
"uniqueItems": true
}
},
"required": ["id", "name", "price"],
"**confidential**" : ["**credit-card**"]
}
Can I have a special keyword called "confidential" to make sure that "credit-card" should not be revealed and/or masked? Does the schema standards allow for such custom metadata keywords?
It depends on the library you will use to execute the validation. I've used AJV, it allows you to define custom keywords, you can find more information about it here.

Recursive JSON Schema

I'm trying to create proper JSON Schema for menu with sub-menus.
So I should define an array from item which should contain three items.
1 Display name, 2 URL and Children (which should be an array of object with the same structure)
At this time I've got this:
{
"type": "array",
"additionalProperties": false, // have no idea what is this for :)
"items": {
"type": "object",
"additionalProperties": false, // have no idea what is this for :)
"description": "MenuLink",
"id": "menuLink",
"properties": {
"display_name": {
"type": "string",
"title": "Link display name",
"minLength": 2
},
"url": {
"type": "string",
"title": "URL address",
"minLength": 2
},
"children": {
"type": "array",
"title": "Childrens",
"additionalItems": false, // have no idea what is this for :)
"items": {
"$ref": "menuLink"
}
}
},
"required": [
"display_name",
"url"
]
}
}
The problem is that it valid only for the first level of the menu
Any help will be appreciated
additionalProperties in arrays does nothing, it is simply ignored. For object it doesn't allow any other properties that are not defined in 'properties'
This schema may or may not work depending on the validator. Reference resolution is the trickiest subject that very few validators do correctly. Check out this: https://github.com/ebdrup/json-schema-benchmark
The more traditional approach to creating what you want:
{
"definitions": {
"menuLink": {
"type": "object",
"additionalProperties": false,
"properties": {
"display_name": {
"type": "string",
"title": "Link display name",
"minLength": 2
},
"url": {
"type": "string",
"title": "URL address",
"minLength": 2
},
"children": {
"type": "array",
"title": "Childrens",
"items": { "$ref": "#/definitions/menuLink" }
}
},
"required": [ "display_name", "url" ]
}
},
"type": "array",
"items": { "$ref": "#/definitions/menuLink" }
}
use definitions and $ref.
Use this online json/schema editor to check your schema visually.
Paste the schema code to the Schema area and click Update Schema.
editor screenshot:
------------------>>>
schema code:
{
"definitions": {
"MenuItem": {
"title": "MenuItem",
"properties": {
"display_name": {
"type": "string",
"title": "Link display name",
"minLength": 2
},
"url": {
"type": "string",
"title": "URL address",
"minLength": 2
},
"children": {
"type": "array",
"title": "Children",
"items": {
"$ref": "#/definitions/MenuItem"
}
}
}
}
},
"title": "MenuItems",
"type": "array",
"items": {
"$ref": "#/definitions/MenuItem"
}
}

JSON Schema Validation - Why is required not being enforced?

I've been trying to create a JSON Schema and have been using an online validation tool http://jsonschemalint.com/. I made a change to my JSON object that I expected to fail against my schema but it didn't - so I think I must have made a mistake somewhere. Can anyone explain to my why the following change doesn't cause a validation error?
Schema
{
"title": "Control Configuration Array",
"description": "An array of the configurations",
"type": "array",
"minItems": 1,
"items": {
"group": {
"title": "Control Grouping",
"description": "Represents a logical grouping of controls",
"type": "object",
"properties": {
"value": {
"title": "Group Label",
"description": "The label to use for the group",
"type": "string"
},
"sortIndex": {
"title": "Sort Index",
"description": "The order in which the groups appear",
"type": "number",
"minimum": 0
},
"cssClass": {
"title": "Group CSS",
"description": "The CSS class to apply to the group label",
"type": "string"
},
"controls": {
"title": "Controls",
"description": "The set of controls within this group",
"type": "array",
"minItems": 1,
"required": true,
"items": {
"config": {
"title": "Control Configuration",
"description": "The main configuration object for a control and its associated dependencies",
"type": "object",
"properties": {
"id": {
"title": "Control ID",
"description": "The identifier for the control set which will be used in dependencies",
"type": "string",
"required": true
},
"sortIndex": {
"title": "Sort Index",
"description": "The order in which the controls appear",
"type": "number",
"minimum": 0
},
"label": {
"title": "Label",
"description": "Describes the label for the control group",
"type": "object",
"properties": {
"value": {
"title": "Caption",
"description": "The caption to place in the label",
"type": "string"
},
"cssClass": {
"title": "Label CSS Classes",
"description": "The CSS classes to apply to the label, separated with spaces",
"type": "string"
},
"tooltipText": {
"title": "Tooltip",
"description": "The tooltip to apply to the label and control",
"type": "string"
}
}
},
"control": {
"title": "Control",
"description": "Describes the control for the control group",
"type": "object",
"required": true,
"properties": {
"type": {
"title": "Control Type",
"description": "The type of control that should be displayed",
"type": "string",
"enum": [
"text",
"radio",
"dropdown",
"checkbox",
"color",
"date",
"datetime",
"search",
"email",
"url",
"tel",
"number",
"range",
"month",
"week",
"time",
"datetime-local"
]
},
"options": {
"title": "Avaliable Options",
"description": "The set of avaliable options for all selection controls (e.g. radio, dropdown)",
"type": "array"
},
"value": {
"title": "The current value of the control",
"description": "This is the inital value or selected value of the control",
"type": "object",
"required": true
},
"cssClass": {
"title": "Control CSS Classes",
"description": "The CSS classes to apply to the control, separated with spaces",
"type": "string"
}
}
},
"dependencies": {
"title": "Dependencies",
"description": "Describes the dependencies between this and other controls",
"type": "object",
"properties": {
"enabled": {
"title": "Enabled",
"description": "The properties to determine if the control should be enabled or not",
"type": "object",
"properties": {
"targetID": {
"title": "Enabled Target ID",
"description": "The ID of the target control, whose value must match one of the target values for this control to be enabled",
"type": "string",
"required": true
},
"targetValues": {
"title": "Enabled target values",
"description": "The set of values which if selected in the target control will cause this control to be enabled",
"type": "array",
"required": true
}
}
},
"display": {
"title": "Display",
"description": "The properties to determine if the control should be displayed or not",
"type": "object",
"properties": {
"targetID": {
"title": "Display Target ID",
"description": "The ID of the target control, whose value must match one of the target values for this control to be displayed",
"type": "string",
"required": true
},
"targetValues": {
"title": "Display target values",
"description": "The set of values which if selected in the target control will cause this control to be displayed",
"type": "array",
"required": true
}
}
}
}
},
"validation": {
"title": "Validation",
"description": "Describes the validation of the control value",
"type": "object",
"properties": {
"required": {
"title": "Required",
"description": "Whether the field is required",
"type": "boolean"
},
"min": {
"title": "Minimum",
"description": "The minimum value that the control is allowed",
"type": "number"
},
"max": {
"title": "Maximum",
"description": "The maximum value that the control is allowed",
"type": "number"
},
"minLength": {
"title": "Minimum Length",
"description": "The minimum length that the control is allowed",
"type": "integer"
},
"maxLength": {
"title": "Maximum Length",
"description": "The maximum length that the control is allowed",
"type": "integer"
},
"pattern": {
"title": "Regex Pattern",
"description": "A regex pattern to use for validation",
"type": "string"
},
"step": {
"title": "Increment Step",
"description": "An increment check that must be met - generally combine with min/max",
"type": "number"
},
"email": {
"title": "Email",
"description": "Whether the field must be an email address",
"type": "boolean"
},
"equal": {
"title": "Equals",
"description": "Ensure the field equals the other object",
"type": "object"
},
"notEqual": {
"title": "Not Equals",
"description": "Ensure the field does not equal the other object",
"type": "object"
},
"date": {
"title": "Date",
"description": "Whether the field must be a date",
"type": "boolean"
},
"dateISO": {
"title": "Date ISO",
"description": "Whether the field must be an ISO date",
"type": "boolean"
},
"number": {
"title": "Number",
"description": "Whether the field must be a number",
"type": "boolean"
},
"digit": {
"title": "Digit",
"description": "Whether the field must be a digit",
"type": "boolean"
}
}
}
}
}
}
}
}
}
}
}
JSON
[
{
"value": "Group1",
"cssClass": "red",
"sortIndex": 1,
"controls": [
{
"id": "ConfigType",
"sortIndex": 1,
"label": {
"value": "Configuration Type",
"cssClass": "label",
"tooltipText": "Configuration Type Tooltip"
},
"control": {
"type": "radio",
"options": [
"Single Deck",
"Level"
],
"value": "Single Deck",
"cssClass": "control"
}
},
{
"id": "AppType",
"sortIndex": 2,
"label": {
"value": "Application Type",
"cssClass": "label",
"tooltipText": "Application Type Tooltip"
},
"control": {
"type": "dropdown",
"options": [
"Other",
"Other2"
],
"value": "Other",
"cssClass": "red"
},
"dependencies": {
"enabled": {
"targetID": "ConfigType",
"targetValues": [
"Level"
]
},
"display": {
"targetID": "ConfigType",
"targetValues": [
"Level"
]
}
}
},
{
"id": "textType",
"label": {
"value": "Text Type",
"cssClass": "label",
"tooltipText": "text Type Tooltip"
}
}
]
}
]
Change
Find "targetID" : "ConfigType" under either the enabled or display dependency. Remove this line. This should then fail as these are both required fields according to the schema. However it doesn't seem to fail...
First I would recommend you move to draft 4 which have some improvements (required is provided in an array).
jsonschemalint is using draft 3. Afaik "items" constraint has not changed. You can provide a boolean value, an object or one array of objects.
In your case, you have provided one object-schema but incorrectly. "group" and "config" labels are not necessary. For instance, given the following json object in draft 3:
[{}]
This schema (similar as yours) validates the data:
{
"type" : "array",
"minItems" : 1,
"items" : {
"unnecesaryLabel" : {
"type" : "object",
"properties" : {
"one" : {
"required" : true
}
}
}
}
}
And this makes the data invalid:
{
"type" : "array",
"minItems" : 1,
"items" : {
"type" : "object",
"properties" : {
"one" : {
"required" : true
}
}
}
}