Metaschema specifying required attribute for all properties - json

I want to customize a metaschema such that all properties are required to have an additional attribute, for example, how could I require that all properties specify a "type"?
Then this schema should fail:
{
"$schema": "http://json-schema.org/schema#",
"title": "...",
"description": "...",
"type": "object",
"properties": {
"name": {
"description": "..."
}
}
}
But this one should succeed:
{
"$schema": "http://json-schema.org/schema#",
"title": "...",
"description": "...",
"type": "object",
"properties": {
"name": {
"description": "...",
"type": "string"
}
}
}

Unfortunately, writing meta-schemas is not easy. It's being worked on, but there's no good solution yet.
You would have to make a copy of the meta-schema you want to extend and then add "required": ["type"].
But, while we're here, maybe I can convince you not to do this. Making the type keyword required causes problems in some cases. Here are a few examples. https://github.com/json-schema/json-schema/issues/172#issuecomment-124214534
EDIT
After discussing this further, we found that this particular case doesn't have the problems we normally run into with extending meta-schemas because it doesn't need to be recursive. Here is an example of extending the draft-06 schema to include a new keyword called custom which is a boolean and is required only at the top level of a properties schema.
{
"allOf": [
{ "$ref": "http://json-schema.org/draft-06/schema#" },
{
"properties": {
"properties": {
"patternProperties": {
".*": {
"properties": {
"custom": { "type": "boolean" }
},
"required": ["custom"]
}
}
}
}
}
]
}
And here's an example schema that conforms to this meta-schema.
{
"properties": {
"foo": {
"custom": true,
"not": { "type": "string" }
}
}
}
The custom keyword is required for the "foo" schema, but not the not schema or the top level schema.

Related

Is it possible to reference to JSON data itself within a schema definition?

Is it possible to define a JSON schema to check inner data integrity? So that mistakes like "ba" could be detected without further runtime checks.
Data JSON
{
"childNames": ["foo", "bar", "baz"],
"children": [
{
"name": "foo"
},
{
"name": "ba"
}
]
}
JSON Schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://tbd.com/foo/bar",
"type": "object",
"properties": {
"childNames": {
"type": "array",
"items": { "type": "string" }
},
"children": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"enum": { "$ref": "???" }
}
}
}
}
}
}
If you're using .Net, then you can do this with JsonSchema.Net and JsonSchema.Net.Data.
You'll need to use a custom vocabulary which defines the data keyword.
Your schema will need to change to:
{
"$schema": "https://gregsdennis.github.io/json-everything/meta/data",
"$id": "https://tbd.com/foo/bar",
"type": "object",
"properties": {
"childNames": {
"type": "array",
"items": { "type": "string" }
},
"children": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"data": {
"enum": "#/childNames"
}
}
}
}
}
}
}
Note the data keyword where your ??? was and the different meta-schema in the $schema keyword.
You can try it on https://json-everything.net/json-schema.
Information on how this works is here.
If you're not using .Net, you may need to figure out how to support this vocabulary (or work with the maintainer of the library you're using to do so).
Disclaimer: I'm the creator & maintainer of JsonSchema.Net and the other json-everything libraries.

JSON Schema `required` allows empty string for value

I'm using a JSON schema template to validate the data that is received by an online form. One of the requirements of the validator is that it allows some questions to be required based on the answers given for other questions.
For example if the question is Do you want a loan? and the user answers yes, then the answer to the question What is the loan to be used for? needs to be set to required so that the user must provide an answer. If the answer is no then the second question is not required.
I'm using definitions to define my questions, and then referencing them below in the main question schema. I read that by using the if-then-else feature provided in draft-07 I could use it to set certain questions to be required based on answers to other questions.
In this particular instance what I would like to happen is that if the user enters the answer Home improvements (General) for question 9, then question 257 will be set to required and MUST be answered, otherwise an error should be thrown.
At the moment, when I enter this validator into https://www.jsonschemavalidator.net/ it does not work as expected. What actually happens is the answer for question 257 can be left blank even if the answer to question 9 is "Home improvements (General)
How can I change my schema to give the behaviour I am trying to get?
JSON Schema
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"question3-9": {
"type": "object",
"properties": {
"answer": {
"type": "string",
"enum": [
"Home improvements (General)",
"Other"
]
}
}
},
"question3-257": {
"type": "object",
"properties": {
"answer": {
"type": "string",
}
}
}
},
"type": "object",
"properties": {
"form_submission": {
"type": "object",
"properties": {
"sections": {
"type": "object",
"properties": {
"3": {
"type": "object",
"properties": {
"questions": {
"type": "object",
"properties": {
"9": {
"$ref": "#/definitions/question3-9"
},
"257": {
"$ref": "#/definitions/question3-257"
}
},
"if": {
"properties": {
"9": {
"properties": {
"answer": {
"enum": [
"Home improvements (General)"
]
}
}
}
}
},
"then": {
"required": [
"257"
]
}
}
}
}
},
"required": [
"3"
]
}
}
}
}
}
JSON to be validated:
{
"form_submission": {
"sections": {
"3": {
"questions": {
"9": {
"answer": "Home improvements (General)",
},
"257": {
"answer": "",
}
}
}
}
}
}
Updated If-Then
"if": {
"properties": {
"9": {
"properties": {
"answer": {
"enum": [
"Home improvements (General)"
]
}
},
"required": ["answer"]
}
},
"required": ["9"]
},
"then": {
"257": {
"properties":{
"answer":{
"minLength": 1
}
}
}
}
Your problem here is you are expecting required to check the value of the key, which it does not.
Required from the current draft-7 specification:
An object instance is valid against this keyword if every item in the
array is the name of a property in the instance.
This means required only checks that the key exists for the object.
It is not related to the value.
For string validation, see the validation key words which are applicable to strings. I suspect you want minLength or pattern (which is regex).
https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-validation-01#section-6.3

How to use Switch in JSON Schema 6 with multiple IFs

I have entity which is of array. I want to call different entity types based on a property called dataStoreType. My Schema looks like this:
"entities":{
"type":"array",
"switch":[
{
"if": {"properties":{"dataStoreType":"RELATIONAL"}},
"then":{"$ref": "#/definitions/entities-relational"}
},
{
"if": {"properties":{"dataStoreType":"DOCUMENT"}},
"then":{"$ref": "#/definitions/entities-no-sql"}
},
{
"if": {"properties":{"dataStoreType":"KEYVALUE"}},
"then":{"$ref": "#/definitions/entities-key-value"}
}
]
}
My instance JSON looks like this:
{
"name": "document-simple",
"dataStoreType": "RELATIONAL",
"entities": [
{
"name": "Employee",
"attributes": [
{
"name": "firstName",
"type": "STRING",
"required": true
},
{
"name": "lastName",
"type": "STRING"
},
}
]
}
But my Schema is not validating this instance correctly because I think there is some error in the switch. I am sure that JSON is not being validated because I have defined other rules for entities(which I have not mentioned) and when my instance violates that, the schema is not showing error.
What could be the error in my switch
Your main problem is that where you have:
"properties": {"dataStoreType": "RELATIONAL"}
what you actually need is:
"properties": {"dataStoreType": {"const": "RELATIONAL"}}
Beyond that, note that switch is no longer a JSON Schema proposal, but if/then/else has been accepted for the next draft (draft-07). So when using Ajv or another validator with early if support turned on, or moving to draft-07 when it is out and supported, you want:
"entities":{
"type":"array",
"oneOf": [
{
"if": {"properties":{"dataStoreType":{"const":"RELATIONAL"}}},
"then":{"$ref": "#/definitions/entities-relational"}
},
{
"if": {"properties":{"dataStoreType":{"const":"DOCUMENT"}}},
"then":{"$ref": "#/definitions/entities-no-sql"}
},
{
"if": {"properties":{"dataStoreType":{"const":"KEYVALUE"}}},
"then":{"$ref": "#/definitions/entities-key-value"}
}
]
}
This is the equivalent of a switch (without fall-through) or a chained set of else-ifs (which also works, but gets harder to read the more you chain).
Your if conditions are mutually exclusive, so oneOf is appropriate here and will do what you want.
I would definitely avoid using switch, as it has been pretty thoroughly rejected as a proposal for the standard. It is unlikely to ever be added.
JSON Schema Draft 6 does not support the "if" and "then" keywords. It is currently a proposal. Probably you use an implementation which already supports it (ajv, maybe?).
On the other hand you can achieve what you want using "oneOf" and "const" in the following way:
{
"oneOf" : [
{
"type": "object",
"properties": {
"dataStoreType": {
"const": "RELATIONAL"
},
"entities": {
"type": "array",
"items": {
"$ref": "#/definitions/entities-relational"
}
}
}
},
{
"type": "object",
"properties": {
"dataStoreType": {
"const": "DOCUMENT"
},
"entities": {
"type": "array",
"items": {
"$ref": "#/definitions/entities-no-sql"
}
}
}
},
// and here comes the KEYVALUE schema.. I think you get it now
]
}

JSON Schema v4 "required" in nested object

I tried searching, but I'm not quite sure how to put this in words! The point of confusion is how "required" works in JSON schema v4 when there are nested key values with the same name.
For example, this schema:
{
"Root": {
"type": ["array", "null"],
"items": {
"type": "object",
"properties": {
"LevelOne": {
"required": ["id", "name", "LevelOneRepeat"],
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"LevelOneRepeat": {
"type": ["array", "null"],
"items": {
"type": "object",
"properties": {
"required": ["id", "name"],
"id": {
"type": "string"
},
"name": {
"type": "string"
}
}
}
}
}
}
}
}
}
Inside LevelOne, I have a required for "id", "name", and "LevelOneRepeat". However, inside LevelOneRepeat, I also have a required for "id" and "name".
Does required only check for elements in the same level, or also all child elements? Do I need to include the required inside LevelOneRepeat if the key values required (same name) are already listed in the level above?
I tested my JSON with my schema, but I might've messed up my code somewhere as no required are working anymore.
You have a couple of issues with your schema that is probably what has led to your confusion about how required works.
Here is the corrected schema.
{
"type": ["array", "null"],
"items": {
"type": "object",
"properties": {
"LevelOne": {
"type": "object",
"properties": {
"id": { "type": "string" },
"name": { "type": "string" },
"LevelOneRepeat": {
"type": ["array", "null"],
"items": {
"type": "object",
"properties": {
"id": { "type": "string" },
"name": { "type": "string" }
},
"required": ["id", "name"]
}
}
},
"required": ["id", "name", "LevelOneRepeat"],
}
}
}
}
The first problem is with the way you define nested objects. The value of each property must be a schema. See how I changed the LevelOne definition to see how to correctly define a nested object.
The second problem is that have the required keyword in the wrong place. It should be on the same level as the properties keyword, not nested within the properties object. This is why your required constraints weren't working. See how I changed the LevelOne and LevelOneRepeat definitions.
Once you fix these problems, hopefully it should be more clear that the required keyword only applies to the schema it is defined in. It does not apply to any parent or child schema.
The id in LevelOne and LevelOneRepeat are only coincidentally related. They have to be independently specified as required.

How to tell JSON schema validator to pick schema from property value?

For example a schema for a file system, directory contains a list of files. The schema consists of the specification of file, next a sub type "image" and another one "text".
At the bottom there is the main directory schema. Directory has a property content which is an array of items that should be sub types of file.
Basically what I am looking for is a way to tell the validator to look up the value of a "$ref" from a property in the json object being validated.
Example json:
{
"name":"A directory",
"content":[
{
"fileType":"http://x.y.z/fs-schema.json#definitions/image",
"name":"an-image.png",
"width":1024,
"height":800
}
{
"fileType":"http://x.y.z/fs-schema.json#definitions/text",
"name":"readme.txt",
"lineCount":101
}
{
"fileType":"http://x.y.z/extended-fs-schema-video.json",
"name":"demo.mp4",
"hd":true
}
]
}
The "pseudo" Schema note that "image" and "text" definitions are included in the same schema but they might be defined elsewhere
{
"id": "http://x.y.z/fs-schema.json",
"definitions": {
"file": {
"type": "object",
"properties": {
"name": { "type": "string" },
"fileType": {
"type": "string",
"format": "uri"
}
}
},
"image": {
"allOf": [
{ "$ref": "#definitions/file" },
{
"properties": {
"width": { "type": "integer" },
"height": { "type": "integer"}
}
}
]
},
"text": {
"allOf": [
{ "$ref": "#definitions/file" },
{ "properties": { "lineCount": { "type": "integer"}}}
]
}
},
"type": "object",
"properties": {
"name": { "type": "string"},
"content": {
"type": "array",
"items": {
"allOf": [
{ "$ref": "#definitions/file" },
{ *"$refFromProperty"*: "fileType" } // the magic thing
]
}
}
}
}
The validation parts of JSON Schema alone cannot do this - it represents a fixed structure. What you want requires resolving/referencing schemas at validation-time.
However, you can express this using JSON Hyper-Schema, and a rel="describedby" link:
{
"title": "Directory entry",
"type": "object",
"properties": {
"fileType": {"type": "string", "format": "uri"}
},
"links": [{
"rel": "describedby",
"href": "{+fileType}"
}]
}
So here, it takes the value from "fileType" and uses it to calculate a link with relation "describedby" - which means "the schema at this location also describes the current data".
The problem is that most validators do not take any notice of any links (including "describedby" ones). You need to find a "hyper-validator" that does.
UPDATE: the tv4 library has added this as a feature
I think cloudfeet answer is a valid solution. You could also use the same approach described here.
You would have a file object type which could be "anyOf" all the subtypes you want to define. You would use an enum in order to be able to reference and validate against each of the subtypes.
If the sub-types schemas are in the same Json-Schema file you don't need to reference the uri explicitly with the "$ref". A correct draft4 validator will find the enum value and will try to validate against that "subschema" in the Json-Schema tree.
In draft5 (in progress) a "switch" statement has been proposed, which will allow to express alternatives in a more explicit way.