Why required is not an element property? - json

Current schema example:
{
"$schema": "http://json-schema.org/schema#",
"type": "object",
"properties": {
"id": {
"type": "string",
"uniqueItems": true
},
"name": {
"type": "string"
},
"age": {
"type": "number"
},
"description": {
"type": "string"
}
},
"required": ["id", "name", "age"]
}
This to me is counterintuitive. It requires to repeat the property names and repetition is bad. I would have expected this instead:
{
"$schema": "http://json-schema.org/schema#",
"type": "object",
"properties": {
"id": {
"type": "string",
"uniqueItems": true,
"required": true
},
"name": {
"type": "string",
"required": true
},
"age": {
"type": "number",
"required": true
},
"description": {
"type": "string"
}
}
}
Is there a technical reason for required being an array where you have to repeat the property names? Is this approach superior in any way?

The set of required keys is an attribute of an object, not of its individual properties. That is, a predefined property
{
...
"$defs": {
"age_property": {
"type": "number"
}
}
...
}
may be required by one object
{
"type": "object",
"properties": {
"age": { "$ref": "#/$defs/age_property" },
...
},
"required": ["age", ...]
}
but not another
{
"type": "object",
"properties": {
"age": { "$ref": "#/$defs/age_property" },
...
},
"required": [...]
}

tl;dr
It has to do with what the keyword is actually evaluating. It's evaluating the container for the property's presence; the subschema in /properties is checking the value, if there is one.
Explanation
(source: I'm one of the specification authors and a validator implementor)
required used to be a keyword that was contained inside a property definition. As of draft 4, it was moved to it's own root-level keyword.
The value inside properties is always to be a schema. This subschema should stand alone, unaware that it's contained within a larger schema. As a schema, its function is to evaluate a value, but it has no knowledge of the origin of the value. In the case of properties, this is a value from a key-value pair. Again, it has no knowledge of the key or the object that contains it.
If required were part of the property definition, it would be validating not the value of the property, but the object that contains it. This is the responsibility of the parent schema.
An example:
// schema
{
"type": "object",
"properties": {
"a": { "type": "string" },
"b": { "required": true }
}
}
// instance
{ "b": "some value" }
/properties/b ({"required":true}) is instructed to evaluate "some value". How can required know that this value comes from an object and is under the b property? It would need knowledge of the value's parent to do that. (JSON Schema validators had to bend themselves into funny shapes in order to support this.)
The solution was to move required out of the property and into the schema that is evaluating the object itself.
// schema
{
"type": "object",
"properties": {
"a": { "type": "string" }
},
"required": [ "b" ]
}
// instance
{ "b": "some value" }
Now, required can evaluate the full object, and it can check whether that object contains a b property. Because there is no /properties/b in this case, any value is fine, so long as b is present.
Unfortunately, the discussion around moving this keyword has been lost as the current GitHub repo was set up after the move from draft 3 to draft 4.

The written specification is not based on real world application. See https://github.com/json-schema-org/json-schema-spec/issues/725 from the one who's probably done most practical use of json schema (ajv lib's author).
There is no right or wrong about the approach, but it is going to be useful for wide range of application or not is questionable. There are TONS of debates around this specification.
IMO, yes required makes impossible state possible (out-of-sync)

there is no technical reason, who designed jsonschema decided to use an array instead of element properties, pheraps because in this way you have all the required elements name near standing.

Related

JSON Schema: using property value defined in another object

I have two properties in JSON Schema:
"A": {"type": "object", "properties":{ "X":{"type":"boolean"}}}
"B": {"type": "object", "properties":{...}}
In the logic of B I need to use value of X to set B property values and "required":[...].
How do I use value of X within the B ?
You need to declare a rule at the schema level that defines the object that contains these two properties. You can then use the dependentSchemas, dependentRequired, or if/then/else keywords to define rules like "if <something> in property A, then <something> in property B".
https://json-schema.org/understanding-json-schema/reference/conditionals.html
For example:
{
"type": "object",
"properties": {
"A": {
"type": "object",
"properties":{ "X":{"type":"boolean"}}
},
"B": {
"type": "object",
"properties":{...}
}
},
"if": {
"required": ["A"],
"properties": {
"A": {
"required": ["X"],
"properties": { "X": { "const": true } }
}
}
},
"then": {
"required": ["B"],
"properties": {
"B": {
"required": ["Y"],
"properties": { "Y": { "const": "/A/X is true" } }
}
}
}
}
This is saying "if property A exists, and has a subproperty X which is true, then there must be a property Y under B which is the string "/A/X is true"".
Speaking about the standard specification of JSON Schema in general, you can't "use" values from the JSON instance at some other place for validation in the meaning that, for example, you take a value and fill a required keyword with it.
However, you can define conditions using if-then-else that use values to decide what other schemas should be applied subsequently at this point in the validation flow.
The dependentSchemas and dependentRequired keywords are focused on the presence of properties.
More details are available here.

How to make a required property if another property is empty and vice versa, in the JSON Schema

My current json schema definition is like this
{
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1
},
"description": {
"type": "string",
"minLength": 1
},
"input": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"description": {
"type": "string"
},
"type": {
"type": "string"
}
},
"required": ["name", "description", "type"]
}
},
"output": {
"type": "array",
"maxItems": 1,
"items": {
"type": "object",
"properties": {
"description": {
"type": "string",
"minLength": 1
},
"type": {
"type": "string"
}
},
"required": ["description", "type"]
}
}
},
"required": ["name", "description"]
}
So I need to validate the scheme for the following conditions:
If input array and output array are empty, both must be required;
If the input array is not empty, then the output array should not be required;
If the output array is not empty, then the input array should not be required;
Thank you in advance.
Your first condition is the only one that we need to deal with. All properties are optional by default, so your conditions 2 and 3 translate to something like, "if the input array is not empty, then do nothing".
There are a couple of ways to achieve the first condition, but I suggest the following.
"allOf": {
"if": {
"properties": {
"input": { "const": [] },
"output": { "const": [] }
}
},
"then": { "required": ["input", "output"] }
}
it seems like all three of your requirements are self-fulfilling in json schema.
If input array and output array are empty, both must be required
if input and output are empty arrays, they are already present, so saying they are required is redundant. sort of, "if x is present with the value [], then x must be present". Jason's schema correctly expresses the way you've phrased this, but I don't think there's any way for that schema to cause a validation error.
and Jason's answer is correct on points 2 and 3.
I'd suggest you think about some example instances you would expect to fail validation (and add them to your question here), and that will help to construct a schema that adds the proper constraints.

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.

Define a map type using json-schema v4

I'm trying to define a referenced schema for use as a Cassandra CQL map type with text fields. Specifically, I want to map URIs to strings.
Right now I have:
"scope": {
"type": "object",
"properties": {
"uri": {
"type": "string",
"format": "uri"
},
"permission": {
"type": "string",
"enum": ["read_only", "read_write", "write_only"]
}
},
"required": ["uri", "permission"],
"additionalProperties": false
}
This is good for data like
{"uri":"http://example.com",
"permission": "read_only"}
But I want a schema for data like
{"http://example.com": "read_only"}
http://spacetelescope.github.io/understanding-json-schema/reference/object.html has a solution:
{
"type": "object",
"patternProperties": {
"^S_": { "type": "string" },
"^I_": { "type": "integer" }
}
}
The problem with this is that I'd have to define a built-in format with a regular expression. Looking at examples of regexs for URIs makes me want to avoid this.
Since the number of URIs I have are limited, mapping enum to enum would also be a solution. Is that doable?
If I can be permitted to answer my own question, I believe the solution is to use a PatternProperties definition for the key, with a very specific regex. The value can be any type supported by json-schema, including another regex. In my case, it's an enum.
So the definition looks something like-
"patternProperties": {
"^https:\/\/www.example.com\/auth\/\\w+$": {
"type": "string",
"enum": ["read_only", "read_write", "write_only"]
}
},
"additionalProperties": false

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.