JSON-Schema: Conditional dependency (by value) - json

This is the simplified JSON-Schema:
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "user",
"type": "object",
"properties": {
"account": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["COMPANY", "PERSON"]
}
},
"required": ["type"]
},
"person": {
"type": "object",
"properties": {
"firstName": { "type": "string" },
"lastName": { "type": "string" }
},
"required": ["firstName", "lastName"]
},
"company": {
"type": "object",
"properties": {
"name": { "type": "string" },
"taxNumber": { "type": "string" }
}
}
},
"required": ["account", "person"]
}
What I want to achieve is:
If account.type is set to "COMPANY":
company object and its properties should be required.
If account.type isset to "PERSON":
company object should be optional.
But if company object is present, company.name and company.taxNumber should be required.
This can be achieved by defining two long sub-schemas under a oneOf but that would mean too many duplicates and a complex schema, since account and company has many more properties than this simplified version.
AFAIK, the only way to define a specific value in a schema is by using the enum keyword with a single item. I tried this with the dependencies keyword but didn't help.
Can you think of a way without altering the structure of the data object?

You can express this requirement using switch keyword from JSON-schema v5/6 proposals that is supported in Ajv (I am the author).

Under Draft-07, you can do it by conditionally applying schema requirements:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"id": "user",
"type": "object",
"properties": {
"account": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["COMPANY", "PERSON"]
}
},
"required": ["type"]
},
"person": {
"type": "object",
"properties": {
"firstName": {
"type": "string"
},
"lastName": {
"type": "string"
}
},
"required": ["firstName", "lastName"]
},
"company": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"taxNumber": {
"type": "string"
}
}
}
},
"if": {
"properties": {
"account": {
"const": {
"type": "COMPANY"
}
}
}
},
"then": {
"required": ["account", "person", "company"]
},
"else": {
"required": ["account", "person"]
}
}
Here's a working example that validates against it:
{
"account": {
"type": "COMPANY"
},
"person": {
"firstName": "John",
"lastName": "Doe"
},
"company": {
"name": "XYZ Corp"
}
}

Related

Validate JSON Schema based on "type" property

I have the following JSON which validates against my current schema:
{
"information": [
{
"value": "closed",
"type": "power"
},
{
"value": "on",
"type": "door"
}
]
}
However, I want this validation to fail (since open/closed should relate to door, and on/off should only relate to power.
How can I tie the two together?
My current working schema:
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"definitions": {
"Type": {
"type": "string",
"enum": ["power", "door"]
},
"Power": {
"type": "string",
"enum": ["on", "off"]
},
"Door": {
"type": "string",
"enum": ["open", "closed"]
},
"InformationField": {
"type": "object",
"required": [ "type", "value" ],
"properties": {
"label": { "type": "string" },
"value": {
"anyOf": [
{ "$ref": "#/definitions/Power"},
{ "$ref": "#/definitions/Door"}
]
},
"type": { "$ref": "#/definitions/Type" }
}
},
"Information": {
"type": "array",
"items": { "$ref": "#/definitions/InformationField" }
},
},
"properties": {
"information": { "$ref": "#/definitions/Information" }
},
}
I have a lot of types, so I want to do this in the cleanest way possible.
Here is one of my attempts, which fails to validate correctly:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"definitions": {
"Type": {
"type": "string",
"enum": ["power", "door"]
},
"Power": {
"type": "string",
"enum": ["on", "off"]
},
"Door": {
"type": "string",
"enum": ["open", "closed"]
},
"InformationField": {
"type": "object",
"required": [ "label", "value" ],
"properties": {
"label": { "type": "string" },
"type": { "$ref": "#/definitions/Type" }
},
"anyOf": [
{
"if": {
"properties": { "type": { "const": "power" } }
},
"then": {
"properties": { "value": { "$ref": "#/definitions/Power" } }
}
},
{
"if": {
"properties": { "type": { "const": "door" } }
},
"then": {
"properties": { "value": { "$ref": "#/definitions/Door" } }
}
}
]
},
"Information": {
"type": "array",
"items": { "$ref": "#/definitions/InformationField" }
},
},
"properties": {
"information": { "$ref": "#/definitions/Information" }
},
}
(Validates, even though it shouldn't ...)
Changed anyOf to allOf in my attempt, which fixes validation.
My reasoning as to why this works:
From the anyOf JSON schema spec:
5.5.4.2. Conditions for successful validation
An instance validates successfully against this keyword if it validates successfully against at least one schema defined by this keyword's value.
If my first if condition is false, then it doesn't evaluate the then, and is therefore valid.
Using allOf evaluates every condition's validation (not the condition itself).

Assistance needed to create JSON schema

As I am new to JSON schema creation, I have learnt the basics of JSON schema and now I am trying to create JSON schema for the below mentioned set of data,
{
"Result": [
{
"ResourceName": "Appointment",
"Sequence": "1",
"Data": {
"AppointmentID": "A1234",
"PatientName": "Test Patient",
"ClinicName": "Test Clinic"
}
},
{
"ResourceName": "EpisodeofCare",
"Sequence": "2",
"Data": {
"EpisodeID": "EP1234",
"LocationId": "L1234",
"AppointmentId": "A1234",
"TransactionStatus": "2",
"OPNumber": "OP523367"
}
},
{
"ResourceName": "Encounter",
"Sequence": "3",
"Data": {
"EncounterID": "E1234",
"PatientID": "P1234"
}
}
]
}
Can anybody please help me to create JSON schema for this kind of data set.
Thanks in advance for helping me out on this.
Below is the JSON schema i have drafted
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"ResultType": {
"type": "object",
"properties": {
"ResourceName": {
"type": "string"
},
"Sequence": {
"type": "string"
},
"Data": {
"type": "object",
"anyOf": [
{
"$ref": "#/definitions/Appointment"
},
{
"$ref": "#/definitions/EpisodeofCare"
},
{
"$ref": "#/definitions/Encounter"
}
]
}
}
},
"Appointment": {
"type": "object",
"properties": {
"AppointmentID": {
"type": "boolean"
},
"PatientName": {
"type": "string"
},
"ClinicName": {
"type": "string"
}
}
},
"EpisodeofCare": {
"type": "object",
"properties": {
"EpisodeID": {
"type": "string"
},
"LocationId": {
"type": "string"
},
"AppointmentId": {
"type": "string"
},
"TransactionStatus": {
"type": "string"
},
"OPNumber": {
"type": "string"
}
}
},
"Encounter": {
"type": "object",
"properties": {
"EncounterID": {
"type": "string"
},
"PatientID": {
"type": "string"
}
}
}
},
"type": "object",
"properties": {
"Result": {
"type": "array",
"$ref": "#/definitions/ResultType"
}
}
}
Throw it in a schema validator like this one.
You will see errors in your schemas definition and in validation.
First of all check that your schema is valid json (the validator might help):
[
{
{"$ref": "#/definitions/Appointment"} <- unnecessary and invalid braces
},
{
{"$ref": "#/definitions/EpisodeofCare"} <- unnecessary and invalid braces
},
{
{"$ref": "#/definitions/Encounter"} <- unnecessary and invalid braces
}
]
Within the Result property you want each item of the array. You should use the item property instead of ref. As taken from the specification:
The value of "items" MUST be either a valid JSON Schema or an array of valid JSON Schemas.
[...]
If "items" is a schema, validation succeeds if all elements in the array successfully validate against that schema.
Thus you should use
{
"type": "object",
"properties": {
"Result": {
"type": "array",
"items": {
"$ref": "#/definitions/ResultType"
}
}
}
}
Since the schema should be as specific as possible you should constraint the content of the ResourceName properties to the actual data layout.
Use the const property to define a constant value for the ResourceName for each type of Resource. Take for example the Appointment type, which would change to:
"Appointment": {
"type": "object",
"properties": {
"ResourceName": {
"const": "Appointment"
},
"Data": {
"type": "object",
"properties": {
"AppointmentID": {
"type": "string"
},
"PatientName": {
"type": "string"
},
"ClinicName": {
"type": "string"
}
}
}
}
I also changed the type of AppointmentID to string.
Then your ResultType needs to be adjusted. Before you used anyOf to validate the data, now you need it to validate the complete object while still maintaining the overall structure:
"ResultType": {
"allOf": [
{
"type": "object",
"properties": {
"ResourceName": {
"type": "string"
},
"Sequence": {
"type": "string"
},
"Data": true
}
},
{
"anyOf": [
{
"$ref": "#/definitions/Appointment"
},
{
"$ref": "#/definitions/EpisodeofCare"
},
{
"$ref": "#/definitions/Encounter"
}
]
}
]
}
The allOf propertiy implies a and condition. The overall structure (the object) does not care what value ResourceName has or how Data is structured. The specific implementations (the anyOf part) take care of that.
Some final improvements. Use the required property to define the names of properties that must exist in your JSON file. Also add "additionalProperties": false to ensure that only the specified properties are used. And finally constraint your sequence values to be string representations of positive intergers by adding "pattern": "[1-9]\\d*".
Final result:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"ResultType": {
"allOf": [
{
"type": "object",
"required": [
"ResourceName",
"Sequence",
"Data"
],
"additionalProperties": false,
"properties": {
"ResourceName": {
"type": "string"
},
"Sequence": {
"type": "string",
"pattern": "[1-9]\\d*"
},
"Data": true
}
},
{
"anyOf": [
{
"$ref": "#/definitions/Appointment"
},
{
"$ref": "#/definitions/EpisodeofCare"
},
{
"$ref": "#/definitions/Encounter"
}
]
}
]
},
"Appointment": {
"type": "object",
"properties": {
"ResourceName": {
"const": "Appointment"
},
"Data": {
"type": "object",
"required": [
"AppointmentID",
"PatientName",
"ClinicName"
],
"additionalProperties": false,
"properties": {
"AppointmentID": {
"type": "string"
},
"PatientName": {
"type": "string"
},
"ClinicName": {
"type": "string"
}
}
}
}
},
"EpisodeofCare": {
"type": "object",
"properties": {
"ResourceName": {
"const": "EpisodeofCare"
},
"Data": {
"type": "object",
"required": [
"EpisodeID",
"LocationId",
"AppointmentId",
"TransactionStatus",
"OPNumber"
],
"additionalProperties": false,
"properties": {
"EpisodeID": {
"type": "string"
},
"LocationId": {
"type": "string"
},
"AppointmentId": {
"type": "string"
},
"TransactionStatus": {
"type": "string"
},
"OPNumber": {
"type": "string"
}
}
}
}
},
"Encounter": {
"type": "object",
"properties": {
"ResourceName": {
"const": "Encounter"
},
"Data": {
"type": "object",
"required": [
"EncounterID",
"PatientID"
],
"additionalProperties": false,
"properties": {
"EncounterID": {
"type": "string"
},
"PatientID": {
"type": "string"
}
}
}
}
}
},
"type": "object",
"required": [
"Result"
],
"additionalProperties": false,
"properties": {
"Result": {
"type": "array",
"items": {
"$ref": "#/definitions/ResultType"
}
}
}
}
you can create json schema by online tools which generates json schema from the json
like: https://www.liquid-technologies.com/online-json-to-schema-converter , it also checks if json is valid or not.
Json Schema that you need:
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"Result": {
"type": "array",
"items": [
{
"type": "object",
"properties": {
"ResourceName": {
"type": "string"
},
"Sequence": {
"type": "string"
},
"Data": {
"type": "object",
"properties": {
"AppointmentID": {
"type": "string"
},
"PatientName": {
"type": "string"
},
"ClinicName": {
"type": "string"
}
},
"required": [
"AppointmentID",
"PatientName",
"ClinicName"
]
}
},
"required": [
"ResourceName",
"Sequence",
"Data"
]
},
{
"type": "object",
"properties": {
"ResourceName": {
"type": "string"
},
"Sequence": {
"type": "string"
},
"Data": {
"type": "object",
"properties": {
"EpisodeID": {
"type": "string"
},
"LocationId": {
"type": "string"
},
"AppointmentId": {
"type": "string"
},
"TransactionStatus": {
"type": "string"
},
"OPNumber": {
"type": "string"
}
},
"required": [
"EpisodeID",
"LocationId",
"AppointmentId",
"TransactionStatus",
"OPNumber"
]
}
},
"required": [
"ResourceName",
"Sequence",
"Data"
]
},
{
"type": "object",
"properties": {
"ResourceName": {
"type": "string"
},
"Sequence": {
"type": "string"
},
"Data": {
"type": "object",
"properties": {
"EncounterID": {
"type": "string"
},
"PatientID": {
"type": "string"
}
},
"required": [
"EncounterID",
"PatientID"
]
}
},
"required": [
"ResourceName",
"Sequence",
"Data"
]
}
]
}
},
"required": [
"Result"
]
}

JSON Schema for strict objects in an array

I have created a JSON schema to validate a simple JSON file. The good news is that it validates in the way that I intended, in that any number of booking elements can appear in any order, and no extra properties are allowed in each type of booking element.
Ideally I would like to remove the full list of possible properties in a bookingElement object (id, type, depair, destair, city) in the JSON schema, and just leave the oneOf lists, which show clearly which fields are allowed in each different type of element.
Can anyone provide a version of the schema without that full list that still applies the strict rules?
This is the JSON:
{
"bookingElements": [
{
"id" : "00003",
"type" : "flight",
"depair" : "LHR",
"destair" : "CDG"
},
{
"id" : "00008",
"type" : "hotel",
"city" : "Paris"
}
]
}
The schema is:
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"additionalProperties": false,
"properties": {
"bookingElements": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"id": {
"type": "string"
},
"type": {
"type": "string"
},
"depair": {
"type": "string"
},
"destair": {
"type": "string"
},
"city": {
"type": "string"
}
},
"oneOf": [
{
"type": "object",
"additionalProperties": false,
"properties": {
"id": {
"type": "string"
},
"type": {
"type": "string"
},
"depair": {
"type": "string"
},
"destair": {
"type": "string"
}
}
},
{
"type": "object",
"additionalProperties": false,
"properties": {
"id": {
"type": "string"
},
"type": {
"type": "string"
},
"city": {
"type": "string"
}
}
}
]
}
}
},
"required": [
"bookingElements"
]
}
Ideally the JSON schema would look something closer to the following:
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"additionalProperties": false,
"properties": {
"bookingElements": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"oneOf": [
{
"type": "object",
"additionalProperties": false,
"properties": {
"id": {
"type": "string"
},
"type": {
"type": "string"
},
"depair": {
"type": "string"
},
"destair": {
"type": "string"
}
}
},
{
"type": "object",
"additionalProperties": false,
"properties": {
"id": {
"type": "string"
},
"type": {
"type": "string"
},
"city": {
"type": "string"
}
}
}
]
}
}
},
"required": [
"bookingElements"
]
}

Json schema auto completion property

I have already created my own schema on intellij environment, and it's working good, but still have problems in auto completion which provides intellij to the schema,
for example if object "car" defined in json schema then intellij can recognize that there's such object in the schema and intellij will give it as suggestion through out coding json, the problem that I'm facing is that the suggestions are contains all the objects that defined in the schema, but the expectations are to get the objects which defined under the scoop of another object
This is some code of my own schema:
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Pipe File",
"type": "object",
"definitions": {
"Pipe": {
"type": "object",
"properties": {
"components": {
"$ref": "#/definitions/components"
}
},
"required": [
"components"
]
},
"components": {
"description": "section which defines the pipes in the file",
"type": "array",
"minItems": 1,
"items": {
"oneOf": [
{
"$ref": "#/definitions/setValuesComponent"
},
{
"$ref": "#/definitions/invokeWebServicesComp"
}
]
}
},
"setValuesComponent": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"type": {
"enum": [
"setValuesComp"
]
},
"out": {
"type": "object",
"properties": {
"dateFormat": {
"$ref": "#/definitions/setValuesCompOut"
},
"dateTimeFormat": {
"$ref": "#/definitions/setValuesCompOut"
},
"dateFormatBank": {
"$ref": "#/definitions/setValuesCompOut"
}
}
},
"condition": {
}
},
"required": [
"name",
"type",
"out"
]
},
"setValuesCompOut": {
"type": "object",
"properties": {
"exprValue": {
"type": "string"
},
"ctxEntry": {
"type": "string"
},
"value": {
"type": "string"
},
"exprConst": {
"type": "string",
"pattern": "(Class|class)\\.\\w+\\.\\w+"
}
},
"anyOf": [
{
"required": [
"exprValue"
]
},
{
"required": [
"ctxEntry"
]
},
{
"required": [
"value"
]
},
{
"required": [
"exprConst"
]
}
]
},
"invokeWebServicesComp": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"type": {
"enum": [
"invokeWebServices"
]
},
"mode": {
"enum": [
"innerJoin",
"leftJoin",
"union",
"parallelJoin"
]
},
"method": {
"type": "string"
},
"headers": {
"$ref": "#/definitions/invokeWebServicesCompHeaders"
},
"dataFilePath": {
"type": "string"
},
"restRelativeUrl": {
"type": "string"
},
"in": {
"$ref": "#/definitions/invokeWebServicesCompIn"
},
"out": {
"$ref": "#/definitions/invokeWebServicesCompOut"
}
},
"required": [
"type",
"name",
"out",
"in"
]
},
"invokeWebServicesCompOut": {
"type": "object",
"patternProperties": {
"doc": {
"type": "string",
"pattern": ".+"
}
}
},
"invokeWebServicesCompHeaders": {
"type": "object",
"patternProperties": {
".{1,}": {
"type": "string",
"pattern": ".+"
}
}
},
"invokeWebServicesCompIn": {
"type": "object",
"patternProperties": {
".{1,}": {
"type": "string",
"pattern": ".+"
}
}
},
"properties": {
"pipes": {
"description": "section which defines the mandatory pipes object in the file",
"type": "object",
"patternProperties": {
".{1,}": {
"$ref": "#/definitions/Pipe"
}
}
}
},
"required": [
"pipes"
]
}
}
So what I expected is, when the type of object determined to "setValuesComp", the auto completion will suggest the relevant properties, that's mean it will not suggest "in" property which is belong to "invokeWebServicesComp" not "setValuesComponent".this picture show the auto complete problem in my real environment
Your JSON schema seems to be invalid. The below JSON content should be present inside object type.
"properties": {
"pipes": {
"description": "section which defines the mandatory pipes object in the file",
"type": "object",
"patternProperties": {
".{1,}": {
"$ref": "#/definitions/Pipe"
}
}
}
},
"required": [
"pipes"
]
In your schema, it is present as part of the "definitions". Please make this correction and then check if you are able to get the suggestions.

ajv conditional schema validation based on data

I would like to specify a regexp pattern for one field based on the data in another. Is this possible? I've tried switch and $data but not sure how to use them.
for example, if data looks like:
{
"contacts":[
{
"mode":"Email",
"contact":"john.doe#abc.com"
},
{
"mode":"Phone",
"contact":"111-555-1234"
}
]
}
and schema looks something like:
"$schema":"http://json-schema.org/draft-04/schema#",
"type":"object",
"properties":{
"Contacts":{
"type":"array",
"minItems":1,
"items":{
"type":"object",
"properties":{
"mode":{
"type":"string",
"enum":[
"Email",
"Phone"
]
},
"contact":{
"type":"string",
"pattern":"?????"
}
},
"required":[
"mode",
"contact"
]
}
}
}
}
How can I set the pattern of contact based on data in mode, so that if mode is Email, it validates contact against a regexp for an email format, and if mode is Phone, it validates contact against a regexp for a phone format? I have the regexp for each. I need the logic to choose one or the other.
There are several ways to do it
anyOf (pros: draft-04 compatible, cons: error reporting is a bit verbose - you will get errors from both subschemas if none matches):
{
"type": "object",
"properties": {
"Contacts": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"anyOf": [
{
"properties": {
"mode": {"enum": ["Email"]},
"contact": {
"type": "string",
"format": "email"
}
}
},
{
"properties": {
"mode": {"enum": ["Phone"]},
"contact": {
"type": "string",
"pattern": "phone_pattern"
}
}
}
],
"required": ["mode", "contact"]
}
}
}
}
if/then/else (available in ajv-keywords package, pros: error reporting makes more sense, accepted to be included in draft-07, cons: not standard at the moment):
{
"type": "object",
"properties": {
"Contacts": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"mode": {"type": "string", "enum": ["Email", "Phone"]},
"contact": {"type": "string"}
},
"if": {
"properties": {
"mode": {"enum": ["Email"]}
}
},
"then": {
"properties": {
"contact": {"format": "email"}
}
},
"else": {
"properties": {
"contact": {"pattern": "phone_pattern"}
}
}
"required": ["mode", "contact"]
}
}
}
}
select (available in ajv-keywords package, pros: more concise than if/then/else, particularly if there are more than two possible values, cons: not on the standard track yet, but you can support it :), requires enabling $data reference and Ajv v5.x.x):
{
"type": "object",
"properties": {
"Contacts": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"mode": {"type": "string"},
"contact": {"type": "string"}
},
"select": { "$data": "0/mode" },
"selectCases": {
"Email": {
"properties": {
"contact": {"format": "email"}
}
},
"Phone": {
"properties": {
"contact": {"pattern": "phone_pattern"}
}
}
},
"selectDefault": false,
"required": ["mode", "contact"]
}
}
}
}
I prefer the last option.