In json schema, how to chain definitions? - json

I'm trying to build a json schema with "chained" definition.
For the given schema :
{
"$schema": "http://json-schema.org/schema#",
"id": "http://toto/filter-schema#",
"title": "filter schema definition",
"type": "object",
"description": "filter data",
"definitions": {
"timeFilters": {
"type": "object",
"oneOf": [
{
"properties": {
"since": {
"type": "string",
"format": "date-time",
"description": "sends only items that have been modified AFTER this"
}
},
"additionalProperties": false,
"required": [
"since"
]
},
{
"properties": {
"from": {
"type": "string",
"format": "date-time",
"description": "return only data that have a validity date AFTER this"
},
"to": {
"type": "string",
"format": "date-time",
"description": "return only data that have a validity date BEFORE this"
}
},
"additionalProperties": false,
"required": [
"from",
"to"
]
}
]
},
"objectFilters": {
"type": "object",
"properties": {
"objectFilters": {
"type": "array",
"description": "the array of object filter",
"minItems": 1,
"uniqueItems": true,
"items": [
{
"type": "object",
"additionalProperties": false,
"properties": {
"targetClass": {
"type": "string",
"description": "the target class"
},
"targetAttribute": {
"type": "string",
"description": "the target attribute"
},
"test": {
"type": "string",
"description": "the test on the attribute value"
}
}
}
]
}
},
"additionalProperties": false
}
},
"anyOf": [
{
"additionalProperties": false
},
{
"$ref": "#/definitions/timeFilters",
"additionalProperties": false
},
{
"$ref": "#/definitions/objectFilters",
"additionalProperties": false
}
]
}
this schema is validating
{
"since": "2016-02-17T01:02:03.1Z"
}
and
{
"from": "2016-02-17T01:02:03.1Z",
"to": "2016-02-17T01:02:03.1Z",
}
and
{
"objectFilters": [
{
"targetClass": "test",
"targetAttribute": "test",
"test": "test"
}
]
}
and even
{}
but not
{
"since": "2016-02-17T01:02:03.1Z",
"objectFilters": [
{
"targetClass": "test",
"targetAttribute": "test",
"test": "test"
}
]
}
How can make it validating the last json ?
I have tried to add a new definition to the "anyOf", something like this :
{
"$ref": "#/definitions/timeFilters",
"$ref": "#/definitions/objectFilters",
"additionalProperties": false
}
but it does not work.
I use the draft v4.
EDIT :
Also tried
{
"$ref": "#/definitions/timeFilters",
"additionalProperties": {
"$ref": "#/definitions/objectFilters",
"additionalProperties": false
}
}
not working either

The behavior of additionalProperties can get confusing when combining schemas. I suggest a different approach. It's easier to declare all your properties upfront and then declare the required properties constraints separately.
The dependencies keyword works well in this case.
http://json-schema.org/latest/json-schema-validation.html#anchor70
{
"$schema": "http://json-schema.org/schema#",
"id": "http://toto/filter-schema#",
"title": "filter schema definition",
"description": "filter data",
"type": "object",
"properties": {
"since": {
"type": "string",
"format": "date-time",
"description": "sends only items that have been modified AFTER this"
},
"from": {
"type": "string",
"format": "date-time",
"description": "return only data that have a validity date AFTER this"
},
"to": {
"type": "string",
"format": "date-time",
"description": "return only data that have a validity date BEFORE this"
},
"objectFilters": {
"type": "array",
"description": "the array of object filter",
"minItems": 1,
"uniqueItems": true,
"items": [
{
"type": "object",
"additionalProperties": false,
"properties": {
"targetClass": {
"type": "string",
"description": "the target class"
},
"targetAttribute": {
"type": "string",
"description": "the target attribute"
},
"test": {
"type": "string",
"description": "the test on the attribute value"
}
}
}
]
}
},
"additionalProperties": false,
"dependencies": {
"since": {
"not": { "required": ["from"] }
},
"from": ["to"],
"to": ["from"]
}
}

Related

JSON Schema validation for different property values

I am attempting to validate the following JSON file:
{
"Transaction": {
"Header": {
"Workflow": "Rejection",
"Job-Offer": {
"Offer-Status": "New",
"Datetime-Offered": "2017-12-15T16:00:00",
"Accepted": "YES",
"Datetime-Accepted": "2017-12-15T16:00:00"
}
}
}
}
against the following schema:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Schema",
"description": "Schema",
"$ref": "#/defs/Schema",
"defs": {
"Schema": {
"type": "object",
"additionalProperties": false,
"properties": {
"Transaction": {
"$ref": "#/defs/Transaction"
}
},
"required": [
"Transaction"
],
"title": "Schema"
},
"Transaction": {
"type": "object",
"additionalProperties": false,
"properties": {
"Transaction-Header": {
"$ref": "#/defs/Transaction-Header"
}
},
"required": [
"Transaction-Header"
],
"title": "Transaction"
},
"Transaction-Header": {
"type": "object",
"additionalProperties": false,
"properties": {
"Workflow": {
"type": "string",
"enum": [
"Offer",
"Transfer",
"Acceptance",
"Rejection",
"Cancellation",
"Update"
]
},
"Job-Offer": {
"$ref": "#/defs/JobOffer"
}
},
"required": [
"Workflow"
],
"title": "Transaction-Header"
},
"JobOffer": {
"description": "Job Offer.",
"type": "object",
"additionalProperties": true,
"properties": {
"Offer-Status": {
"type": "string",
"enum": [
"New",
""
]
},
"Datetime-Offered": {
"type": "string",
"format": "date-time"
},
"Accepted": {
"type": "string",
"enum": [
"YES",
"NO",
""
]
},
"Datetime-Accepted": {
"type": "string",
"format": "date-time"
},
"Reason-Rejected": {
"type": "string",
"minLength": 0,
"maxLength": 30
},
"Offer-Cancelled": {
"type": "string",
"enum": [
"YES",
"NO",
""
]
},
"Datetime-Cancelled": {
"type": "string",
"format": "date-time"
}
},
"allOf": [
{ "$ref": "#/defs/JOBACCEPT" },
{ "$ref": "#/defs/JOBREJECT" }
],
"required": [
"Offer-Status"
],
"title": "JobOffer"
},
"JOBACCEPT": {
"properties": {
"Workflow": { "enum": [ "Acceptance" ] }
},
"required": [
"Accepted",
"Datetime-Accepted"
],
},
"JOBREJECT": {
"properties": {
"Workflow": { "enum": [ "Rejection" ] }
},
"required": [
"Reason-Rejected"
],
}
}
}
What I am after is:
If the Workflow of "Acceptance" is selected, the fields under JOBACCEPT are required.
If the Workflow of "Rejection" is selected, the fields under JOBREJECT are required.
I have tried many different combinations of oneOf, allOf, anyOf, if-then-else but nothing seems to work correctly.
Anyone have any ideas what needs to be done?
Re-worked json inline:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/product.schema.json",
"type": "object",
"properties": {
"Transaction": {
"type": "object",
"properties": {
"Transaction-Header": {
"type": "object",
"properties": {
"Workflow": {
"type": "string",
"enum": [
"Offer",
"Transfer",
"Acceptance",
"Rejection",
"Cancellation",
"Update"
]
},
"Job-Offer": {
"type": "object",
"properties": {
"Offer-Status": {
"type": "string",
"enum": [
"New",
""
]
},
"Datetime-Offered": {
"type": "string",
"format": "date-time"
},
"Accepted": {
"type": "string",
"enum": [
"YES",
"NO",
""
]
},
"Datetime-Accepted": {
"type": "string",
"format": "date-time"
},
"Reason-Rejected": {
"type": "string",
"minLength": 0,
"maxLength": 30
},
"Offer-Cancelled": {
"type": "string",
"enum": [
"YES",
"NO",
""
]
},
"Datetime-Cancelled": {
"type": "string",
"format": "date-time"
}
},
"required": [
"Offer-Status"
]
},
"readOnly": true
},
"required": [
"Workflow"
]
}
},
"required": [
"Transaction-Header"
]
}
},
"allOf": [
{
"if": {
"properties": {
"Transaction": {
"properties": {
"Transaction-Header": {
"properties": {
"Workflow": {
"enum": [
"Acceptance"
]
}
},
"required": [
"Workflow"
]
}
}
}
}
},
"then": {
"properties": {
"Transaction": {
"properties": {
"Transaction-Header": {
"properties": {
"Job-Offer": {
"properties": {},
"required": [
"Accepted",
"Datetime-Accepted"
]
}
}
}
}
}
}
}
}
],
"required": [
"Transaction"
]}
You had the right idea. The problem is where you've placed the allOf with your conditionals. You have it in the "JobOffer" schema, but are trying to set constraints on the "Workflow" property which is in the "Transaction-Header" schema. There is no way to reference a property that is higher up in the JSON tree structure, so you need to move the allOf up into the "Transaction-Header" schema so you can set constraints on the "Workflow" property.
Now that it's in the right place, the best way to express your conditional constraints is with if/then. The context of the if/then is now the the "Transaction-Header" schema, so the then schemas needs to not just say what properties are required, they need to declare that those properties are in the "Job-Offer" object.
{
...
"defs": {
...
"Transaction-Header": {
...
"allOf": [
{ "$ref": "#/defs/JOBACCEPT" },
{ "$ref": "#/defs/JOBREJECT" }
]
},
"JOBACCEPT": {
"if": {
"type": "object",
"properties": {
"Workflow": { "enum": ["Acceptance"] }
},
"required": ["Workflow"]
},
"then": {
"properties": {
"Job-Offer": {
"required": ["Accepted", "Datetime-Accepted"]
}
}
}
},
"JOBREJECT": { ... Similar to JOBACCEPT ... }
}
}
You're abstracting everything away into definitions *, so it's tricky to express conditionals that reference things multiple layers deep. If you inline all the definitions, it gets a little easier to see what needs to be done.
The if/then keywords need to be at the level of 'Transaction'. In pseudocode:
"if the property 'Header' exists (i.e. required) and its value is ... (const), then require property ... with value (type=object, required properties=[...], property definitions=...)", and so on.
* by the way, in version 2020-12 the definitions keyword is $defs -- it may work the way you have it, but implementations will be unable to validate the schemas undef defs as they won't recognize them there, so some errors can slip through and be harder to find.

JSON-Schema add property to the item conditionally

I have the following JSON data
[
{
"type": "social_media_profiles",
"data": {
"profiles": [
{
"key": "twitter",
"data": "username",
"field": "handle",
"label": "Tweet"
},
{
"key": "customLink",
"data": "abc",
"field": "url",
"label": "Click",
"color": {
"button": "red",
"text": "green",
"border": "yellow"
}
}
]
}
}
]
and following jsong schema
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "array",
"additionalProperties": false,
"items": {
"type": "object",
"required": ["type", "data"],
"additionalProperties": false,
"properties": {
"type": {
"type": "string",
"enum": [
"social_media_profiles"
]
},
"data": {
"type": "object"
}
},
"allOf": [
{
"if": {
"properties": {
"type": {
"const": "social_media_profiles"
}
}
},
"then": {
"properties": {
"data": {
"type": "object",
"required": ["profiles"],
"additionalProperties": false,
"properties": {
"profiles": {
"type": "array",
"items": {
"type": "object",
"required": ["data", "field", "key"],
"additionalProperties": false,
"properties": {
"data": {
"type": "string",
"description": "Data contains either profile url, handle id, etc."
},
"field": {
"type": "string",
"enum": ["url", "handle", "id", "tel"],
"description": "Type of field value."
},
"key": {
"type": "string",
"description": "Social media name used to distinguish each social network"
},
"label": {
"type": "string",
"description": "Label to display on the landing page"
}
},
"allOf": [
{
"if": {
"properties": {
"key": {
"const": "customLink"
}
}
},
"then": {
"properties": {
"color": {
"type": "object",
"additionalProperties": false,
"required": [],
"properties": {
"button": {
"type": "string"
},
"text": {
"type": "string"
},
"border": {
"type": "string"
}
}
}
}
}
}
]
}
}
}
}
}
}
}
]
}
}
I want to add new property color to the profiles item based on the condition when key of the item is customLink.
If key is not customLink then color property should not be there.
Validating the schema from https://www.jsonschemavalidator.net/ is giving error
Found 2 error(s)
Message: JSON does not match all schemas from 'allOf'. Invalid schema indexes: 0.
Schema path: #/items/allOf
Message: JSON does not match schema from 'then'.
Schema path: #/items/allOf/0/then/then
How can I append new property conditionally based on the sibling property value?
In your profiles.items schema, you defined additionalProperties: false.
additionalProperties depends on the properties defined in the same schema object, meaning that color would always be dissallowed.
You can separate out your concerns into "what properties are allowed", and "if their values are valid".
Moving the validation for the color object into profiles.properties allows you to maintain additionalProperties: false in that object level.
The schema values of the properties object are only applied to the instance location for the keys, if they exist (hence needing to use required to require that specific keys are... required).
Having simplified the condional section, you end up with a cleaner Schema.
You now only need to define the condition under which color is required, without worrying about what the value should look like.
Sudo code: if key is customLink, then require color, else dissallow color.
{
"if": {
"properties": {
"key": {
"const": "customLink"
}
}
},
"then": {
"required": [
"color"
]
},
"else": {
"not": {
"required": [
"color"
]
}
}
}
Using not, you can invert the validation result of the applied subschema, which is what you need to do when you want to define that a specific property is NOT allowed.
Here's a live demo to play with: https://jsonschema.dev/s/C9V6N
And for prosperity, here's the full schema, with one or two redundancies removed.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "array",
"items": {
"type": "object",
"required": [
"type",
"data"
],
"additionalProperties": false,
"properties": {
"type": {
"type": "string",
"enum": [
"social_media_profiles"
]
},
"data": {
"type": "object"
}
},
"allOf": [
{
"properties": {
"data": {
"type": "object",
"required": [
"profiles"
],
"additionalProperties": false,
"properties": {
"profiles": {
"type": "array",
"items": {
"type": "object",
"required": [
"data",
"field",
"key"
],
"additionalProperties": false,
"properties": {
"data": {
"type": "string",
"description": "Data contains either profile url, handle id, etc."
},
"field": {
"type": "string",
"enum": [
"url",
"handle",
"id",
"tel"
],
"description": "Type of field value."
},
"key": {
"type": "string",
"description": "Social media name used to distinguish each social network"
},
"label": {
"type": "string",
"description": "Label to display on the landing page"
},
"color": {
"type": "object",
"additionalProperties": false,
"properties": {
"button": {
"type": "string"
},
"text": {
"type": "string"
},
"border": {
"type": "string"
}
}
}
},
"allOf": [
{
"if": {
"properties": {
"key": {
"const": "customLink"
}
}
},
"then": {
"required": [
"color"
]
},
"else": {
"not": {
"required": [
"color"
]
}
}
}
]
}
}
}
}
}
}
]
}
}

JsonSchema: Validate type based on value of another property

I am using the following schema to validate my json:
{
"$schema": "http://json-schema.org/schema#",
"title": " Rules",
"description": "Describes a set of rules",
"type": "object",
"properties": {
"rules": {
"type": "array",
"items": {
"type": "object",
"properties": {
"precedence": {
"type": "number",
"minimum": 0
},
"conditions": {
"type": "array",
"items": {
"type": "object",
"properties": {
"field": {
"type": "string",
"enum": [ "Name", "Size" ]
},
"relation": {
"type": "string",
"enum": [ "is", "is not", "is not one of", "is one of" ]
},
"value": {
"type": ["array", "string", "number"]
}
},
"required": ["field", "relation", "value"],
"additionalProperties": false
}
}
},
"required": ["precedence", "conditions"],
"additionalProperties": false
}
}
},
"required": ["rules"],
"additionalProperties": false
}
I want to set up a dependency to validate that when the value of the relation property has the value is one of or the value is not one of, then the type of the value property can only be array
For example, the following json should not validate because it uses the relation value is not one of and the value property is not an array:
{
"rules": [{
"precedence": 0,
"conditions": [{
"field": "Name",
"relation": "is not one of",
"value": "Mary"
}
]
}
]
}
Is it possible to set up dependencies to validate this way?
The best way to solve these kinds of problems is to separate the complex validation from the rest of the schema using definitions and include it with an allOf. In this solution, I use implication to enforce the validation.
{
"type": "object",
"properties": {
"rules": {
"type": "array",
"items": { "$ref": "#/definitions/rule" }
}
},
"required": ["rules"],
"definitions": {
"rule": {
"type": "object",
"properties": {
"precedence": { "type": "number", "minimum": 0 },
"conditions": {
"type": "array",
"items": { "$ref": "#/definitions/condition" }
}
},
"required": ["precedence", "conditions"]
},
"condition": {
"type": "object",
"properties": {
"field": { "enum": ["Name", "Size"] },
"relation": { "enum": ["is", "is not", "is not one of", "is one of"] },
"value": { "type": ["array", "string", "number"] }
},
"required": ["field", "relation", "value"],
"allOf": [{ "$ref": "#/definitions/array-condition-implies-value-is-array" }]
},
"array-condition-implies-value-is-array": {
"anyOf": [
{ "not": { "$ref": "#/definitions/is-array-condition" } },
{ "$ref": "#/definitions/value-is-array" }
]
}
"is-array-condition": {
"properties": {
"relation": { "enum": ["is not one of", "is one of"] }
},
"required": ["relation"]
},
"value-is-array": {
"properties": {
"value": { "type": "array" }
}
}
}
}
If you are able to use the latest draft-7 version of JSON Schema, you can use if then else, as per https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-validation-00#section-6.6
Although, using oneOf is also a valid approach, it might not be as clear to someone else inspecting your schema at a later date.
I've copied an example from an answer to another question:
If the "foo" property equals "bar", Then the "bar" property is
required
{
"type": "object",
"properties": {
"foo": { "type": "string" },
"bar": { "type": "string" }
},
"if": {
"properties": {
"foo": { "enum": ["bar"] }
}
},
"then": { "required": ["bar"] }
}
(You may want to check the draft support of the library you are using.)
There may be a more concise way to do this, but this will work:
{
"$schema": "http://json-schema.org/schema#",
"title": "Rules",
"description": "Describes a set of rules",
"definitions": {
"field": {
"type": "string",
"enum": ["Name", "Size"]
}
},
"type": "object",
"properties": {
"rules": {
"type": "array",
"items": {
"type": "object",
"properties": {
"precedence": {
"type": "number",
"minimum": 0
},
"conditions": {
"type": "array",
"items": {
"type": "object",
"oneOf": [
{
"properties": {
"field": {
"$ref": "#/definitions/field"
},
"relation": {
"type": "string",
"enum": ["is", "is not"]
},
"value": {
"type": ["string", "number"]
}
},
"required": ["field", "relation", "value"],
"additionalProperties": false
},
{
"properties": {
"field": {
"$ref": "#/definitions/field"
},
"relation": {
"type": "string",
"enum": ["is not one of", "is one of"]
},
"value": {
"type": ["array"]
}
},
"required": ["field", "relation", "value"],
"additionalProperties": false
}
]
}
}
},
"required": ["precedence", "conditions"],
"additionalProperties": false
}
}
},
"required": ["rules"],
"additionalProperties": false
}

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.

Validate property against another with JSON Schema

In the model below, "category_id" property should be required only if "detail" array is empty.
If "detail" array is not empty, "category_id" property is not required.
How can I do this with JSON Schema?
{
"description": "Expense model validation.",
"type": "object",
"properties": {
"description": {
"type": "string"
},
"category_id": {
"type": "string"
},
"detail": {
"type": "array",
"items": {
"description": "Expense detail",
"type": "object",
"properties": {
"description": {
"type": "string"
}
},
"required": [ "description" ]
}
}
},
"required": [ "description", "category_id" ]
}
You can use anyOf to check that either category_id is present, or detail is present and has at least one item.
{
"description": "Expense model validation.",
"type": "object",
"properties": {
"description": { "type": "string" },
"category_id": { "type": "string" },
"detail": {
"type": "array",
"items": {
"description": "Expense detail",
"type": "object",
"properties": {
"description": { "type": "string" }
},
"required": ["description"]
}
}
},
"required": ["description"],
"anyOf": [
{ "required": ["category_id"] },
{
"properties": {
"detail": { "minItems": 1 }
},
"required": ["detail"]
}
]
}