One of the fields in my data is as follows:
{
"license":
"url": "<some url>",
"label": "<some label>"
}
I would like to validate that, for example, if the user provides either of the values for "url":
["http://creativecommons.org/licenses/by/4.0/",
"https://creativecommons.org/licenses/by/4.0/"]
That the value of label must be one of:
["CC-BY", "CC BY 4.0", "CC-BY 4.0"]
And there are multiple different label options, supporting HTTP or HTTPS. I tried the following, but the validation failed (couldn't validate), and I couldn't find anything about corresponding values, just if one field exists, then another must exist (dependencies).
"license": {
"type": "object",
"properties": {
"oneOf": [
{
"url": { "enum": ["http://creativecommons.org/licenses/by/4.0/", "https://creativecommons.org/licenses/by/4.0/"] },
"label": { "enum": ["CC-BY", "CC BY 4.0", "CC-BY 4.0"] }
},
{
"url": { "enum": ["http://creativecommons.org/publicdomain/zero/1.0/", "https://creativecommons.org/publicdomain/zero/1.0/"] },
"label": { "enum": ["CC-0", "CC0", "CC0 1.0 Universal", "CC0 1.0"] }
},
{
"url": { "enum": ["http://creativecommons.org/licenses/by/3.0/", "https://creativecommons.org/licenses/by/3.0/"] },
"label": { "enum": ["CC-BY", "CC-BY 3.0"] }
}
... <and so on>
I tried a couple of different iterations of dependencies/properties in oneOf and nothing seemed to work.
I found the method that works - "oneOf" should be level with "properties" and encase the "properties" objects within it, as below.
"license": {
"type": "object",
"properties": {
"url": { "type": "string", "format": "uri" },
"label": { "type": "string" },
"logo": { "type": "string", "format": "uri" }
},
"required": ["url", "label"],
"oneOf": [
{
"properties": {
"url": { "enum": ["http://creativecommons.org/licenses/by/4.0/", "https://creativecommons.org/licenses/by/4.0/"] },
"label": { "enum": ["CC-BY", "CC BY 4.0", "CC-BY 4.0"] }
}
},
{
"properties": {
"url": { "enum": ["http://creativecommons.org/publicdomain/zero/1.0/", "https://creativecommons.org/publicdomain/zero/1.0/"] },
"label": { "enum": ["CC-0", "CC0", "CC0 1.0 Universal", "CC0 1.0"] }
}
},
{
"properties": {
"url": { "enum": ["http://creativecommons.org/licenses/by/3.0/", "https://creativecommons.org/licenses/by/3.0/"] },
"label": { "enum": ["CC-BY", "CC-BY 3.0"] }
}
},
... <and so on>
Related
I have 3 kinds of product definitions. I want to express choose each product based on its properties. I define the products and then use a oneOf to conditionally pick the product. However, the attributes for the other products are not constrained and can be placed in the json they shouldn't be allowed in. Here is my attempt at displaying the selected product.
{
"type": "object",
"required": [
"type",
"productId"
],
"additionalProperties": false,
"properties": {
"productId": {
"type": "string",
"minLength": 1
},
"type": {
"enum": ["product"]
},
"productInfo": {
"type": "object",
"additionalProperties": false,
"required": [ "type", "gameMode"],
"properties": {
"type": {
"enum": ["digital"]
},
"gameMode": {
"type": "object",
"oneOf": [
{ "$ref": "#/$defs/audioPlayer" },
{ "$ref": "#/$defs/multiPlayer" },
{ "$ref": "#/$defs/console" },
{ "$ref": "#/$defs/controller" }
]
}
}
}
},
"$defs": {
"audioPlayer": {
"type": "object",
"gameMode": {
"enum": ["mp3", "wav"]
}
},
"controller": {
"type": "object",
"properties": {
"mode": {
"enum": ["Retro","XboxElite" ]
},
"batteryLevel": { "type": "string" },
"warranty": { "type": "string" },
"tradeIn": { "type": "boolean" }
}
},
"multiPlayer": {
"type": "object",
"properties": {
"mode": {
"enum": ["LiveXBox","FortNite"]
},
"signUp": {
"type": "object",
"properties": {
"url": { "type": "string" }
}
}
}
},
"console": {
"type": "object",
"properties": {
"mode": {
"enum": ["single", "multi"]
},
"required": {
"type": "boolean"
}
}
}
}
}
This is the JSON that validates successfully against the schema, which is undesirable. I only want the properties to be based on the product.
//This product has properties it shouldn't have
// signUp should be only allowed in multiplayer etc.
{
"type": "product",
"productId": "wwwwww",
"productInfo": {
"type": "digital",
"gameMode": {
"mode": "mp3",
"required": true,
"signUp": "https://mmmmmmm.com",
"warranty": "yes"
}
}
}
The correct examples should be:
// Audio Player properties only
{
"type": "product",
"productId": "audio",
"productInfo": {
"type": "digital",
"gameMode": {
"mode": "mp3", // or "wav"
}
}
}
// Controller properties only
{
"type": "product",
"productId": "controller",
"productInfo": {
"type":"digital",
"gameMode": {
"mode": "Retro", // or XboxElite
"batteryLevel": "high",
"warranty": "yes",
"tradeIn": true
}
}
}
First of all, your $defs/audioPlayer schema is missing the properties keyword. That's not the problem, but I wanted to point it out so it doesn't confuse you later.
The actual problem is that you're missing the "additionalProperties": false constraint on your gameMode types. Your first example doesn't fail because additional properties are ignored by default just like any other schema.
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.
In below json is valid only if type is mobile and endDate is empty/null, else validation need to fail. Here type can have any values but my validation only on mobile type.
Valid json:
{
"contacts": [
{
"type": "mobile",
"endDate": "",
"number": "1122334455"
},
{
"type": "home",
"endDate": "",
"number": "1111122222"
},
{
"type": "mobile",
"endDate": "12-Jan-2017",
"number": "1234567890"
},
]
}
Invalid json: (since contacts don't have a valid mobile number)
{
"contacts": [
{
"type": "mobile",
"endDate": "12-Jan-2021",
"number": "1122334455"
},
{
"type": "home",
"endDate": "",
"number": "1111122222"
},
{
"type": "mobile",
"endDate": "12-Jan-2017",
"number": "1234567890"
},
]
}
Schema which i tried
{
"$schema": "https://json-schema.org/draft/2019-09/schema",
"type": "object",
"properties": {
"contacts": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"type": {
"type": "string"
},
"endDate": {
"type": [
"string",
"null"
]
},
"number": {
"type": "string"
}
},
"anyOf": [
{
"if": {
"properties": {
"type": {
"const": "mobile"
}
}
},
"then": {
"properties": {
"endDate": {
"maxLength": 0
}
}
}
}
]
}
}
}
}
could anyone provide me a right schema for the above json, attaching the example code here, this is valid json but getting error. Invalid json example is here why because type mobile don't have empty endDate.
Thanks in advance.
I found the answer some how, we need not to use anyOf or oneOf. The right one is contains. place of the contains also a mater. working example is here
Here is correct schema
{
"type": "object",
"properties": {
"contacts": {
"type": "array",
"minItems": 1,
"contains": {
"type": "object",
"properties": {
"type": {
"const": "mobile"
},
"endDate": {
"type" : ["string", "null"],
"maxLength": 0
}
},
"required": ["type"]
},
"items": {
"type": "object",
"properties": {
"type": {
"type": "string"
},
"endDate": {
"type": [
"string",
"null"
]
},
"number": {
"type": "string"
}
}
}
}
}
}
Your schema defines properties > data but your instance data uses contacts rather than data. Changing either fixes your problem. You have otherwise done this correctly.
(If you only have one type to check, you do not need to wrap your if/then schema in an anyOf.)
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"
]
}
}
}
]
}
}
}
}
}
}
]
}
}
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).