JSON Schema for child objects with different set of keys - json

I have JSON data of which is an array of data like
[
{
"type": "background_color",
"data": {
"backgroundColor": "F9192D"
}
},
{
"type": "banner_images",
"data": {
"images": [
{
"url": "https://example.com/abc.jpg",
"id": 3085
},
{
"url": "https://example.com/zyx.jpg",
"id": 3086
}
]
}
},
{
"type": "description_box",
"data": {
"text": "Hello 56787"
}
}
]
The data is an array of object which has two keys type and data. The type and keys of the data will be defined by the type of data it has.
Like for background_color type, the data should have backgroundColor property, while for banner_images, data should have images which is an array of other properties.
Till now, What I have done is
{
"definitions": {},
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "array",
"title": "category schema",
"description": "Used to validate data of category",
"examples": [],
"required": [],
"items": {
"type": "object",
"required": [
"type",
"data"
],
"properties": {
"type": {
"type": "string",
"enum": ["background_color", "banner_images", "description_box"]
},
"data": {
"type": "object" // How to define data property here for each use case
}
}
}
}
I'm not getting how to define the data property for each use case?

You can use if/then/else blocks to define conditional constraints.
The values of if and then are schemas. If the if schema is valid, then the then schema is applied, otherwise, the allOf subschema (allOf[0] in this example) would pass validation.
There are a few different ways to do this, but this is clean when you don't have any additional or special requirements. Please come back if you do =]
In this example, I've added banner_images...
You can test it working here.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "array",
"title": "category schema",
"description": "Used to validate data of category",
"items": {
"type": "object",
"required": [
"type",
"data"
],
"properties": {
"type": {
"type": "string",
"enum": [
"background_color",
"banner_images",
"description_box"
]
},
"data": {
"type": "object"
}
},
"allOf": [
{
"if": {
"properties": {
"type": {
"const": "banner_images"
}
}
},
"then": {
"properties": {
"data": {
"required": [
"images"
],
"properties": {
"images": {
"type": "array"
}
}
}
}
}
}
]
}
}
For reference, here's the part of the JSON Schema draft-7 spec document that details the behaviour: https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-validation-01#section-6.6

Related

Conditional References in a JSON Schema

I want to write a single file JSON schema definition with several sub schemas that I can combine, depending on the payload.
The following schema validates, that my schema is working with my sample JSON response. (The response object has a wrong type for payload.role to make sure the schema catches this mistake!)
For clarity, I reduce it on the most important parts. A full working example can be found here: https://www.jsonschemavalidator.net/s/3KAaXjtg
Schema
{
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "http://example.com/baseSchema.json",
"type": "object",
"required": [
"payload"
],
"properties": {
"payload": {
"$id": "#/properties/payload",
"type": "object",
// reference the right schema depending on the payload child key
// if `payload.user` reference `userSchema.json`
// if `payload.users` reference `usersSchema.json`
// if `payload.*` reference `*Schema.json`
"$ref": "userSchema.json"
}
},
"definitions": {
"user": {
"$id": "http://example.com/userSchema.json",
"type": "object",
"required": [
"user"
],
"properties": {
"user": {
"type": "object",
"$ref": "userProperties.json"
}
}
},
"users": {
"$id": "http://example.com/usersSchema.json",
"type": "object",
"required": [
"users"
],
"properties": {
"users": {
"type": "array",
"items": {
"$ref": "userProperties.json"
}
}
}
},
"userProperties": {
"$id": "http://example.com/userProperties.json",
"type": "object",
"properties": {
"firstName": {
"$id": "#/properties/payload/properties/user/properties/firstName",
"type": "string"
}
}
}
}
}
Response
{
"status": {
"code": 200,
"description": "User retrieved successfully."
},
"payload": {
"user": {
"firstName": "Joe",
"lastName": "Doe",
"role": "3", // for testing reasons, this is the wrong type!
"email": "doe#example.com",
"customerID": "",
"projects": [
"AIXG5mEg6QLl9rhVSE6m",
"Bs1bHiOIqKclwwis3CNf",
"NC2OUGVZXU35FA7iwRn4"
],
"status": "Status",
"id": "c555BSZnKLdHSRYqrU5hqiQo733j13"
}
}
}
So I've got a baseSchema.json that matches this response:
{
"status": {},
"payload": {}
}
payload gets extended by a certain key like payload.user = {} or payload.foo = {} and depending on that key, I want to extend schema with one of my definitions.
The following part only works for the key user:
"properties": {
"payload": {
"$id": "#/properties/payload",
"type": "object",
// reference the right schema depending on the payload child key
// if `payload.user` reference `userSchema.json`
// if `payload.users` reference `usersSchema.json`
// if `payload.*` reference `*Schema.json`
"$ref": "userSchema.json"
}
},
I failed to setup any conditions (with allOf, if, else), that would reference the correct sub-schema, based on the payload key.
Any hints and help to solve that is appreciated.
Schema and link to demo at the end... Let's look at how we got there...
In JSON Schema draft-07 and previous, you can't use $ref alongside other keywords. Other keywords are ignored. (In your schema http://example.com/userSchema.json you had type next to $ref). Fortunatly this didn't cause you any problems as you declare the type in the referenced schema. (You CAN do this with 2019-09 or above.)
The values for the keywords if, then, and else are schemas.
For the then subschema to be applied to your instance location, the if schema must come back as valid. If it fails, the else subschema value will be applied.
Our if condition checks for the presense of a specific key.
If the key exists, THEN apply the schema which references the correct schema.
Because you want the conditions to be mutually exclusive, you need to wrap the multiple conditions in a oneOf, and add else: false to the conditional checks. false as a schema makes validation fail.
Let me know if you want any further clarification on any of the above.
Demo: https://jsonschema.dev/s/HLniL
{
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "http://example.com/baseSchema.json",
"type": "object",
"required": [
"payload"
],
"properties": {
"payload": {
"$id": "#/properties/payload",
"type": "object",
"oneOf": [
{
"if": {
"required": [
"user"
]
},
"then": {
"$ref": "userSchema.json"
},
"else": false
},
{
"if": {
"required": [
"users"
]
},
"then": {
"$ref": "usersSchema.json"
},
"else": false
}
]
}
},
"definitions": {
"user": {
"$id": "http://example.com/userSchema.json",
"type": "object",
"required": [
"user"
],
"properties": {
"user": {
"$ref": "userProperties.json"
}
}
},
"users": {
"$id": "http://example.com/usersSchema.json",
"type": "object",
"required": [
"users"
],
"properties": {
"users": {
"type": "array",
"items": {
"$ref": "userProperties.json"
}
}
}
},
"userProperties": {
"$id": "http://example.com/userProperties.json",
"type": "object",
"properties": {
"firstName": {
"type": "string"
}
}
}
}
}

Json schema validation for object which may have two forms

I'm trying to figure out how to validate a JSON object which may have 2 forms.
for examples
when there is no data available, the JSON could be
{
"student": {}
}
when there is data available, the JSON could be
{
"student":{
"id":"someid",
"name":"some name",
"age":15
}
}
I wrote the JSON schema in this way, but it seems not working
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "http://json-schema.org/draft-07/schema#",
"title": "JSON schema validation",
"properties": {
"student": {
"type": "object",
"oneOf": [
{
"required": [],
"properties": {}
},
{
"required": [
"id",
"name",
"age"
],
"properties": {
"id": {
"$id": "#/properties/student/id",
"type": [
"string",
"null"
]
},
"name": {
"$id": "#/properties/student/name",
"type": [
"string",
"null"
]
},
"age": {
"$id": "#/properties/student/age",
"type": [
"number"
]
}
}
}
]
}
}
}
I was wondering is there a way to validate it. Thank you!
An empty properties object, and an empty required object, do nothing.
JSON Schema is constraints based, in that if you don't explicitly constrain what is allowed, then it is allowed by default.
You're close, but not quite. The const keyword can take any value, and is what you want in your first item of allOf.
You can test the following schema here: https://jsonschema.dev/s/Kz1C0
{
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "https://example.com/myawesomeschema",
"title": "JSON schema validation",
"properties": {
"student": {
"type": "object",
"oneOf": [
{
"const": {}
},
{
"required": [
"id",
"name",
"age"
],
"properties": {
"id": {
"type": [
"string",
"null"
]
},
"name": {
"type": [
"string",
"null"
]
},
"age": {
"type": [
"number"
]
}
}
}
]
}
}
}

JSON Schema if-else condition complex scenario

{
"policyHolder": {
"fullName": "A"
},
"traveller": [
{
"fullName": "B",
"relationship": "Spouse"
},
{
"fullName": "A",
"relationship": "My Self"
}
]
}
In above json, I want to validate that
if "relationship" = "My Self" then fullName must match the fullName in policyHolder
A field relationship must exist in traveller array, else json is invalid
I have tried to create a json schema with if-else, allOf, etc. but nothing works which can do these validations but not able to.
Please help!!
Schema:
{
"type": "object",
"required": [
"policyHolder",
"traveller",
],
"properties": {
"policyHolder": {
"$id": "#/properties/policyHolder",
"type": "object",
"required": [
"fullName"
],
"properties": {
"fullName": {
"$id": "#/properties/policyHolder/properties/fullName",
"type": "string",
}
}
},
"traveller": {
"$id": "#/properties/traveller",
"type": "array",
"minItems": 1,
"items": {
"$id": "#/properties/traveller/items",
"type": "object",
"properties": {
"fullName": {
"$ref": "#/properties/policyHolder/properties/fullName"
},
"relationship": {
"$id": "#/properties/traveller/items/properties/relationship",
"type": "string",
}
},
"required": [
"fullName",
"relationship"
],
}
}
}
}```
It's your first requirement that you're going to have the most trouble with. JSON Schema doesn't support validation of data against data elsewhere in the instance. It's a highly discussed topic, but nothing has been adopted yet. I suggest you verify this with a little code.
For the second, I would suggest you extract some of your subschemas into definitions rather than trying to muck about with IDs. IDs are typically more beneficial if you're referencing them from other documents or if you use short (like single-word) IDs. Defining the ID as its location in the document is redundant; most processors will handle this automatically.
{
"type": "object",
"required": [
"policyHolder",
"traveller",
],
"definitions": {
"person": {
"type": "object"
"properties": {
"fullName": {"type": "string"}
},
"required": ["fullName"]
},
"relationship": { "enum": [ ... ] } // list possible relationships
},
"properties": {
"policyHolder": { "$ref": "#/definitions/person" },
"traveller": {
"type": "array",
"minItems": 1,
"items": {
"allOf": [
{ "$ref": "#/definitions/person" },
{
"properties": {
"relationship": { "$ref": "#/definitions/relationship" }
},
"required": ["relationship"]
}
]
}
}
}
}
(I extracted the relationship into its own enum definition, but this is really optional. You can leave it inline, or even an unrestricted string if you don't have a defined set of relationships.)
This can't currently be done with JSON Schema. All JSON Schema keywords can only operate on one value at a time. There's a proposal for adding a $data keyword that would enable doing this kind of validation, but I don't think it's likely to be adopted. $data would work like $ref except it references the JSON being validated rather than referencing the schema.
Here's what how you would solve your problem with $data.
{
"type": "object",
"properties": {
"policyHolder": {
"type": "object",
"properties": {
"fullName": { "type": "string" }
}
},
"traveler": {
"type": "array",
"items": {
"type": "object",
"properties": {
"fullName": { "type": "string" },
"relationship": { "type": "string" }
},
"if": {
"properties": {
"relationship": { "const": "My Self" }
}
},
"then": {
"properties": {
"fullName": { "const": { "$data": "#/policyHolder/fullName" } }
}
}
}
}
}
}
Without $data, you will have to do this validation in code or change your data structure so that it isn't necessary.

JSON Schema: Using anyOf, oneOf, allOf within additionalProperties

I am trying to validate an object that can have arbitrary keys whose values are either an object that looks like:
{ "href": "some string" }
OR an array containing object's matching the above.
Here is what I currently have and DOES NOT WORK:
{
"$schema": "http://json-schema.org/schema#",
"id": "https://turnstyle.io/schema/links.json",
"type": "object",
"additionalProperties": {
"oneOf": [
{
"type": "object",
"required": "href"
},
{
"type": "array",
"items": {
"type": "object",
"required": "href"
}
}
]
}
}
Passing example:
{
"customer": { "href": "/customer/1" },
"products": [
{ "href": "/product/1" },
{ "href": "/product/2" }
]
}
Failing example:
{
"customer": { "wrongKey": "/customer/1" },
"products": "aString"
}
Is it possible, and if so what is the proper syntax?
My assumption is that this will not work because the passing schema(s) in oneOf|anyOf|allOf of additionalProperties must apply to ALL keys falling under additionalProperties.
"required" should be an array of the properties which are mandatory in v4.
Or "required": true (or false) as part of the property in v3.
Try this:
{
"$schema": "http://json-schema.org/schema#",
"id": "https://turnstyle.io/schema/links.json",
"type": "object",
"additionalProperties": {
"oneOf": [
{
"type": "object",
"properties": {
"href": {"type": "string"}
},
"required": ["href"]
},
{
"type": "array",
"items": {
"type": "object",
"properties": {
"href": {"type": "string"}
},
"required": ["href"]
}
}
]
}
}

How to describe this validation requirement in jsonschema?

I want to validate my API json response like this:
{
"code": 0,
"results": [
{"type":1, "abc": 123},
{"type":2, "def": 456}
]
}
I want to validate the objects within results to have a "abc" field when its type is 1, and "def" field when its type is 2. The results may contain arbitrary number of type1 and type2 objects.
Can I specify this in jsonschema? Or must I use a generic validator for elements in results and do validation myself?
You can do this using the anyOf keyword.
An instance validates successfully against this keyword if it validates successfully against at least one schema defined by this keyword's value.
http://json-schema.org/latest/json-schema-validation.html#anchor85
You need to define both types of items and then use anyOf To describe the array items for "results".
{
"type": "object",
"properties": {
"code": { "type": "integer" },
"results": {
"type": "array",
"items": { "$ref": "#/definitions/resultItems" }
}
},
"definitions": {
"resultItems": {
"type": "object",
"anyOf": [
{ "$ref": "#/definitions/type1" },
{ "$ref": "#/definitions/type2" }
]
},
"type1": {
"properties": {
"type": { "enum": [1] },
"abc": { "type": "integer" }
},
"required": ["abc"]
},
"type2": {
"properties": {
"type": { "enum": [2] },
"def": { "type": "integer" }
},
"required": ["def"]
}
}
}