Reusing JSON subschema - json

I am needing to use a sub schema multiple times in my JSON file, but haven't been able to figure out the correct way to structure the schema file such that I am able to get the schema validation on all the sub properties instead of just the property that I list in the schema file.
This question here was getting at a similar question, but the answer didn't make much sense/I wasn't sure if or how I could use the same method here. Am I thinking too much in the OOP mindset with multiple instances of a single class?
Here is more or less what I am trying to do
{
"Object1": {
"Title": "Some Title",
"Description": "Some Description"
},
"Object2": {
"Title": "Another title",
"Description": "Another Description"
}
// unknown number of objects but each object should have the same sub schema
}
Here is what I have thus far
{
"$id": "http://example.com/example.json",
"$schema": "http://json-schema.org/draft-07/schema",
"required": [
"Object1"
],
"title": "The root schema",
"type": "object",
"properties": {
"Object1": {
"required": [
"Title",
"Description"
],
"title": "The Reusable Object schema",
"type": "object",
"properties": {
"Title": {
"title": "The Title schema",
"type": "string"
},
"Description": {
"title": "The Description schema",
"type": "string"
}
},
"additionalProperties": false
}
},
"additionalProperties": true
}

If all values of the object should follow the schema, the solution is quite simple.
First, you have to remember how additionalProperties works...
The value of "additionalProperties" MUST be a valid JSON Schema.
This keyword determines how child instances validate for objects,
and does not directly validate the immediate instance itself.
Validation with "additionalProperties" applies only to the child
values of instance names that do not match any names in "properties",
and do not match any regular expression in "patternProperties".
For all such properties, validation succeeds if the child instance
validates against the "additionalProperties" schema.
https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-validation-01#section-6.5.6
So, now we know that additionalProperties takes a JSON Schema, and not just booleans (booleans are valid JSON Schema), the solution might be a little obvious.
Remove the outermost additionalPropertie, rename properties to additionalProperties, and remove the key Object1 and object braces.
The result is the following...
...
"title": "The root schema",
"type": "object",
"additionalProperties": {
"required": [
"Title",
"Description"
],
...
Live demo: https://jsonschema.dev/s/pqwCc
I don't know what you would want to do with the outer most required though. I guess remove it, as you don't know in advance what the keys will be.
Maybe you want to use minProperties to make sure there is at least one?

Related

JSON scema question on additionalProperties and required

I ran into this situation recently and would like to check my understanding, with a JSON (draft-7) schema.
additionalProperties is set to false, which means every property in our JSON object MUST be listed in the properties array.
the required array contains category, which is not in the properties array.
{
"$schema": "https://json-schema.org/draft-07/schema#",
"$id": "/my-schema-1.0.0",
"description": "My schema",
"type": "object",
"additionalProperties": false,
"properties": {
"value": {
"description": "The value.",
"type": "number"
}
},
"required": [
"value",
"category"
]
}
However, whenever i try to validate that my schema is valid, multiple validators say that it is.
But then i don't think you can create an object that would ever validate successfully against this schema.
I don't see anything within the JSON spec that mentions this conflict. Is anyone able to shed any light on this?

Apply required field to referenced JSON data schema

I have the following use-case I try to solve with JSON schemas.
I have a generic JSON data schema for, for example, a user. Here is an example of the user.schema.json file.
{
"type": "object",
"definitions": {},
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"name": {
"type": "string",
"minLength": 1
},
"email": {
"type": "string",
"minLength": 1
},
"locale": {
"type": "string",
"minLength": 1
},
"active": {
"type": "boolean",
"default": true
},
"password": {
"type": "string",
"minLength": 8
},
"roles": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
}
}
}
}
Now I have 2 different kinds of requests:
- POST: Add a user
- PATCH: Update user data.
In 1 case, I can send this data structure, with 3 required fields, while in case of a patch each field is optional.
So I get the post request file: post-user.schema.json:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$ref": "user.schema.json",
"required": [
"name",
"password",
"email"
]
}
And for my patch (path-user.schema.json:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$ref": "user.schema.json"
}
Now the issue that I am having is that my POST schema also marks a user like:
{
"name": "NoPassword",
"email": "nopassword#moba.nl",
"roles": []
}
Which is missing the required password field, as a valid JSON schema.
Apparently, this is not the way to assign required fields to a referenced data structure. I have tried to use google to see what I can find on the subject regarding this using searches like:
[ how to assign required field to referenced schema's ]
and I tried to obtain this info from the documentation.
I have no luck.
My questions now are:
A. Is it possible to assign required fields to a $referenced json schema data object.
B. If this is possible how to do it
C. If this is not possible, what would be a good way to approach this.
Any help is much appreciated.
Using $ref results in all other properties in the object being ignored, so you need to wrap your use of $ref.
Let's take a look at the spec:
An object schema with a "$ref" property MUST be interpreted as a
"$ref" reference. The value of the "$ref" property MUST be a URI
Reference. Resolved against the current URI base, it identifies the
URI of a schema to use. All other properties in a "$ref" object MUST
be ignored.
https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-01#section-8.3
Then consider the schema you included in your question:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$ref": "user.schema.json",
"required": [
"name",
"password",
"email"
]
}
Reading the spec, you can see why required will be ignored.
Originally $ref was only designed to replace a WHOLE object, not ADD to the conditions for the object.
What you want is for multiple schemas to be applied to the instance. To do this, you use allOf.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"allOf": [
{
"$ref": "user.schema.json"
},
{
"required": [
"name",
"password",
"email"
]
}
]
}
I loaded this schema into a demo for you to test at https://jsonschema.dev - although it doesn't support references yet, so I transcluded the reference, but the validation will work the same.
From draft-8 onwards, $ref will behave as you expect, as it becomes an applicator keyword rather than a keyword with special behaviours, meaning other keywords in the same object will not need to be ignored.

JSON Schema require a specific array element

I defined a list of attributes as json schema:
{
"$schema": "http://json-schema.org/schema#",
"type": "object",
"definitions": {
"attribute": {
"type": "object",
"properties": {
"symbolic-name": { "type":"string"},
"value": { "type":"string"}
},
"required": ["symbolic-name", "value"]
},
"displayname": {
"type": "object",
"properties": {
"symbolic-name": {"enum":["displayName"]},
"value": { "type":"string"}
},
"required": ["symbolic-name", "value"]
}
},
"properties": {
"attributes":{
"type": "array",
// This is the crucial point:
"items": {"oneOf": [
{"$ref": "#/definitions/attribute"},
{"$ref": "#/definitions/displayname"}
]},
"uniqueItems": true
}
}
}
I want to require the list to have exactly one attribute with symbolic-name="displayName"
A valid data object would be:
{
"attributes":[
{"symbolic-name": "displayName", "value": "Display Name"},
{"symbolic-name": "somethingElse", "value": "value1"}
{"symbolic-name": "somethingElse", "value": "value2"}
]
}
Now, this fails to validate as the displayName attribute does not only match "oneOf", but both restrictions. I cannot change it to "allOf", since then all other attributes beside displayName won't match anymore.
In order for your "oneOf" to work, you need your "attribute" and "displayname" schemas to be mutually exclusive- as written, anything that is a valid "displayname" is also a valid "attribute". We can do that by excluding "displayName" as a valid symbolic name for "attribute":
"symbolic-name": {
"type": "string",
"not": {"enum": ["displayName"]}
}
Now elements with a symbolic name of "displayName" can match the "displayname" definition, but will never match the "attribute" definition.
The other part of your question is about having exactly one "displayname" in your array. This is trickier. It also depends on what draft of JSON Schema you are using. 4 and 6 are implemented, and 7 was released on Monday- just declaring "$schema": "http://json-schema.org/schema#" means you are using the most recent one, which would be 7. I recommend using a specific draft for $schema instead of the non-numbered one which may change without notice.
If you are OK with requiring the "displayname" to be the first element of the array, then this would work in any draft (and you don't even need the "oneOf"):
"items": [{"$ref": "#/definitions/displayname"}],
"additionalItems": {"$ref": "#/definitions/attribute"}
Note that "items" is an array here. This means that the first item MUST be a "displayname" and all additional items beyond that first item MUST be "attribute"s.
If you want to allow the "displayname" at any position, that's harder. As of draft-06 there is "contains", which requires at least one item to match the given schema. But there is no easy way to say "at most one item". However, "minContains" and "maxContains" have been suggested for draft-08: https://github.com/json-schema-org/json-schema-spec/issues/441
For now, hopefully you are OK with requiring the first position to be the "displayname", as that will work in all drafts.

Compare object property to validate json schema in same object

I have a very simple object like so:
{
"title": "A registration form",
"description": "A simple form example.",
"type": "object",
"required": [
"firstName",
"lastName"
],
"properties": {
"firstName": {
"type": "string",
"title": "First name"
},
"lastName": {
"type": "string",
"title": "Last name",
"minLength": "{'$ref': '/properties/firstName'}"
},
}
On the property of LastName I would like to compare a value with a value of the property next to it. I am actually comparing integers so in actuality my real world example is even easier, just minimum and maximum.
I looked at the json schema spec here and it seemed like this should be doable, and have tried using relative paths and the $ref object.
Is this not possible?
The reference is here: https://www.rfc-editor.org/rfc/rfc6901
I am using react-jsonschema-form but I don't see where that would effect this.
There are a few of reasons why this doesn't work. The primary reason is that $ref refers to the schema, not the data being validated. There was talk of adding a $data keyword to JSON Schema that would allow referencing the instance data, but I don't think that will even happen.

JSON schema enum vs pattern for single value

I have an sample json:
{
"type": "persons",
"id": 2,
"attributes": {
"first": "something",
"second": "something else"
}
}
And I have to make a schema for it (using JSON API specs and JSON schema docs):
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"type": {
"type": "string",
"pattern": "^persons$"
},
"id": {
"type": "integer"
},
"attributes": {
"type": "object",
"properties": {...}
}
},
"required": ["type", "id", "attributes"]
}
And the question is: if the only acceptable value for "type" is "persons", should I use in schema pattern (like above) or enum like
"enum": ["persons"]
I couldn't get any clear answer from documentation, although in examples in specs enums are used for single values. So what's your opinion?
Ultimately, it doesn't really matter. Both will work and both are reasonable. That said, the most common approach I've seen is to use enum. Neither are perfect for readability, but I think enum is better for two reasons.
Using pattern requires two lines to express. Using enum requires only one because type is implied by the value in the array. Two lines are harder to read than one, so if that line is expressive enough, I say stick with one.
Not everyone is comfortable reading regex. enum might be more accessible for that reason.
Since draft-6 there is a new keyword called const for this use-case.
"type": {
"type": "string",
"const": "persons"
},
http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.1.3