JSON Schema: using property value defined in another object - json

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.

Related

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.

Why required is not an element property?

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.

How can I validate with JSON scheme if object is empty or have required properties?

I want to validate a JSON array of objects with schema in function. These objects must have exactly one of these formats:
empty object
object with four properties
I tried to wrap required properties in oneOf, but I got the following error: Invalid input: data[1].prop should match exactly one schema in oneOf
{
"type": "array",
"items": {
"type": "object",
"properties": {
"prop": {
"type": "object",
"properties": {
"name": {
"prop1": "string"
},
"type": {
"prop2": "string"
},
"amount": {
"prop3": "number"
},
"operation": {
"prop4": "string"
}
},
"oneOf": [
{ "required": ["prop1", "prop2", "prop3", "prop4"] },
{ "required": [] }
]
}
}
}
}
I would move the oneOf out so that it's just under the items keyword.
In one of the subschemas, you have the properties keyword along with the required keyword for those properties plus an additionalProperties: false. This portion would satisfy the "exactly four properties" condition.
In the other subschema, just identify that it needs to be an object, but don't declare any properties. Use additionalProperties: false in this one, too. This satisfies the "empty object" condition.

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

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.

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": ""
}
}