Json Schema dependency on outer field - jsonschema

Given a json structure like this
{
"name": "John Doe",
"billing_address": "123 main st",
"payment_details":{"credit_card": 55555555}
}
I need to make 'billing_address' a required field if payment_details.credit_card exists.
I've started from this example that achieves so if both fields were at the same level of nesting
{
"type": "object",
"properties": {
"name": { "type": "string" },
"credit_card": { "type": "number" }
},
"required": ["name"],
"dependencies": {
"credit_card": {
"properties": {
"billing_address": { "type": "string" }
},
"required": ["billing_address"]
}
}
}
However, what is the syntax to require this field that is one level above the dependant field?

That's too complicated for 'dependencies', so we go back to an if/then/else clause for that. We place these keywords at the top level, where the required field needs to be. In pseudocode: "if there is a payment_details property present, and it has a credit_card property, then require billing_address."
{
...,
"if": {
"type": "object",
"required": [ "payment_details" ],
"properties": {
"payment_details": {
"type": "object",
"required": [ "credit_card" ]
}
},
"then": {
"type": "object",
"required": [ "billing_address" ]
}
}
Note that the "type" and "required" keywords are required here -- as data of different types (for example an array) will cause object-specific keywords like "properties" and "required" to always evaluate to true.

thanks Ether! this worked, but only after adding the credit card as a property, in the if section.
the full if statement looks like this
"if": {
"type": "object",
"required": ["payment_details"],
"properties": {
"payment_details": {
"type": "object",
"properties": {
"credit_card": {}
},
"required": ["credit_card"]
}}},
the full json schema looks like this
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": {
"type": "string"
},
"billing_address": {
"type": "string"
},
"payment_details": {
"type": "object",
"properties": {
"credit_card": {
"type": "integer"
}
}
}
}
,
"if": {
"type": "object",
"required": ["payment_details"],
"properties": {
"payment_details": {
"type": "object",
"properties": {
"credit_card": {}
},
"required": ["credit_card"]
}}},
"then": {
"type": "object",
"required": [ "billing_address" ]
}
}

Related

How can I validate a json schema array that contains a MIXED type of objects?

I have searched and haven't quite found a solution.
I would like to do a schema as so:
...
"bag": {
"type": "array",
"items": {
"anyOf": [
{"$ref": "#/definitions/obj1"},
{"$ref": "#/definitions/obj2"},
{"$ref": "#/definitions/obj3"}
]
},
"required": ["items"],
"minItems": 1
}
...
With objects defined:
...
"definitions": {
"obj1": {
"type": "object",
"properties": {
"obj1": {
"type": "object",
"properties": {
"a": {
"type": "string"
}
},
"required": ["a"]
}
}
},
"obj2": {
"type": "object",
"properties": {
"obj1": {
"type": "object",
"properties": {
"b": {
"type": "string"
}
},
"required": ["b"]
}
}
},
"obj3": {
"type": "object",
"properties": {
"obj1": {
"type": "object",
"properties": {
"c": {
"type": "string"
}
},
"required": ["c"]
}
}
}
}
...
Ideally, I would like to validate against a schema that looks like this:
...
"bag": [
{
"obj1": {"a": "test1"}
},
{
"obj3": {"c": "test1"}
}
]
...
In this context, if someone passes obj1 and obj3 into bag. By the schema, obj1 requires property a and obj3 requires property c.
I'm having trouble actually executing this as the validation doesn't seem to enforce correctly.
Any tips? Thanks in advance.
From your current schema and example data, I can't tell exactly what you want, but making an educated guess...
I suspect you want to use oneOf as opposed to anyOf.
anyOf allows you to match multiple subschemas, and it looks like you only want to allow matching one of the subschemas, obj1, 2, or 3.
This would help you debug the issue, but it's not the cause of your always passing validation.
For each definition subschema, you need to add "additionalProperties": false.
Here's the key: JSON Schema is constraints based, meaning anything not constrained is allowed.
additionalProperties restricts the allowed properties of an object to those defined in properties (and patternProperties).
Here's the example schema. You can see it working with your instance here: https://jsonschema.dev/s/MjBUp
{
"$schema": "http://json-schema.org/draft-07/schema",
"definitions": {
"obj1": {
"type": "object",
"properties": {
"obj1": {
"type": "object",
"properties": {
"a": {
"type": "string"
}
},
"required": ["a"]
}
},
"additionalProperties": false
},
"obj2": {
"type": "object",
"properties": {
"obj1": {
"type": "object",
"properties": {
"b": {
"type": "string"
}
},
"required": ["b"]
}
},
"additionalProperties": false
}
},
"type": "array",
"items": {
"anyOf": [
{"$ref": "#/definitions/obj1"},
{"$ref": "#/definitions/obj2"}
]
},
"required": ["items"],
"minItems": 1
}

Content of an property dependent on another property value

I have the following schema, which "works", but does not enforce all the rules required of it.
I get JSON with a series of questions that have a templateType and data properties. There are different templates for each type, and the type must fit the template (or the client doesn't know how to layout the data).
The schema validates the templateType as an enum, and that the data fits one of the templates, but there's no correlation between the type and data structure (e.g. I could get templateType yesNo and data structure for multiSelect).
I'd like it to validate that the templateType matches the data structure. I can't change the format of the generated JSON, only the schema that validates it. None of the questions I've looked at seem to provide a solution.
For help, the schema can be pasted into the editor at http://jeremydorn.com/json-editor/, which generates a form from the schema and JSON data based on selections and data entered into the form.
{
"definitions": {
"question": {
"type": "array",
"title": "Question",
"items": {
"$ref": "#/definitions/template"
}
},
"template": {
"type": "object",
"title": "Question template",
"required": ["templateType","data"],
"properties": {
"templateType": {
"type": "string",
"enum": ["yesNo","multiSelect"]
},
"data": {
"oneOf": [
{"$ref": "#/definitions/yesNo"},
{"$ref": "#/definitions/multiSelect"}
]
}
}
},
"yesNo": {
"type": "object",
"title": "Yes/No question",
"additionalProperties": false,
"properties": {
"label": {
"type": "string"
}
}
},
"multiSelect": {
"type": "array",
"title": "Multi-select question",
"items": {
"type": "string",
"title": "Label for option",
"additionalProperties": false
}
}
},
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "array",
"items": {
"$ref": "#/definitions/question"
}
}
Have you considered using if, then, else keywords in your schema? They're part of JSON Schema draft-07
It would look like this:
{
"definitions": {
"question": {
"type": "array",
"title": "Question",
"items": {
"$ref": "#/definitions/template"
}
},
"template": {
"type": "object",
"title": "Question template",
"required": ["templateType","data"],
"properties": {
"templateType": {
"type": "string",
"enum": ["yesNo","multiSelect"]
},
"data": {
"if": { "properties": { "templateType": { "pattern": "^yesNo$" } } },
"then": { "$ref": "#/definitions/yesNo" },
"else": { "$ref": "#/definitions/multiSelect" }
}
}
},
"yesNo": {
"type": "object",
"title": "Yes/No question",
"additionalProperties": false,
"properties": {
"label": {
"type": "string"
}
}
},
"multiSelect": {
"type": "array",
"title": "Multi-select question",
"items": {
"type": "string",
"title": "Label for option",
"additionalProperties": false
}
}
},
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "array",
"items": {
"$ref": "#/definitions/question"
}
}
If if/then/else isn't supported by your validator, an alternative could be:
{
"definitions": {
"question": {
"type": "array",
"title": "Question",
"items": {
"$ref": "#/definitions/template"
}
},
"template": {
"type": "object",
"title": "Question template",
"required": ["templateType","data"],
"anyOf": [
{
"properties": {
"templateType": { "type": "string", "pattern": "yesNo" },
"data": { "$ref": "#/definitions/yesNo" }
}
},
{
"properties": {
"templateType": { "type": "string", "pattern": "multiSelect" },
"data": { "$ref": "#/definitions/multiSelect" }
}
}
]
},
"yesNo": {
"type": "object",
"title": "Yes/No question",
"additionalProperties": false,
"properties": {
"label": {
"type": "string"
}
}
},
"multiSelect": {
"type": "array",
"title": "Multi-select question",
"items": {
"type": "string",
"title": "Label for option",
"additionalProperties": false
}
}
},
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "array",
"items": {
"$ref": "#/definitions/question"
}
}

jsonschema with custom property

I want to define a JsonSchema with a customProperty in it, this property follows some rules, so in order to validate those, I need to define a JsonSchema that will validate it.
So far I've managed to describe it properly, but it only works for the first level of attributes, and I want it to be recursive...
From my comprehension it should work, I probably made a mistake I'm unable to see and at this point I don't know if it's a bug, impossible, or stupid...
I believe redefining every type should be a possibility , but obviously I'd rather not.
Here is an example of the Json I want to validate
{
"title": "TheObject",
"type": "object",
"properties": {
"aString": {
"type": "string",
"myCustomProperty": {}
},
"anObjet": {
"type": "object",
"myCustomProperty": {},
"properties": {
"anotherObject": {
"type": "object",
"myCustomProperty": {}, //if this line is removed it still validates which I don't want
"properties": {}
}
}
}
}
}
and here is the JsonSchema I've done so far:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"title": {"type": "string"},
"type": {"type": "string","enum": ["object"]},
"properties": {
"type": "object",
"patternProperties": {
".*": {
"$ref": "#/definitions/Field"
}
}
}
},
"definitions": {
"Field": {
"type": "object",
"properties": {
"type": {
"type": "string"
},
"myCustomProperty": {
"$ref": "#/definitions/myCustomProperty"
},
"patternProperties": {
"^(?!myCustomProperty).*": {
"$ref": "#/definitions/Field"
}
}
},
"required": [
"type",
"myCustomProperty"
]
},
"myCustomProperty": {
//Some rules
}
}
}
I found a solution, I wasn't far from what I wanted after all.
In my definition of "Field", I am describing an object that defines an object, and I was missing the "properties" field. In which I had to put my recursive reference.
The right jsonSchema is the following :
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"title": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"object"
]
},
"properties": {
"type": "object",
"patternProperties": {
".*": {
"$ref": "#/definitions/Field"
}
}
}
},
"definitions": {
"Field": {
"type": "object",
"properties": {
"type": {
"type": "string"
},
"myCustomProperty": {
"$ref": "#/definitions/myCustomProperty"
},
"properties": { <==================== here
"type": "object",
"patternProperties": {
".*": {
"$ref": "#/definitions/Field"
}
}
}
},
"required": [
"type",
"myCustomProperty"
]
},
"myCustomProperty": {/*rules*/}
}
}
So far it works as expected, but if someone has a more elegant suggestion please share!

JSON schema for object with either A and B or C required properties

I have two possible JSON objects for one request:
{
"from": "string",
"to": "string",
"text": "string"
}
or
{
"number": "integer",
"text": "string"
}
In both cases "text" property is optional. Other properties are required (either "number, or both "from" and "to").
What will be the correct JSON schema to validate this?
Here is another solution that I think is a bit more clear. The dependencies clause ensures that "from" and "to" always come as a pair. Then the oneOf clause can be really simple and avoid the not-required boilerplate.
{
"type": "object",
"properties": {
"from": { "type": "string" },
"to": { "type": "string" },
"number": { "type": "integer" },
"text": { "type": "string" }
},
"dependencies": {
"from": ["to"],
"to": ["from"]
},
"oneOf": [
{ "required": ["from"] },
{ "required": ["number"] }
]
}
Finally managed to build the correct scheme.
{
"definitions": {
"interval": {
"type": "object",
"properties": {
"from": {
"type": "string"
},
"to": {
"type": "string"
},
"text": {
"type": "string"
}
},
"required": ["from", "to"],
"not": {
"required": ["number"]
}
},
"top": {
"type": "object",
"properties": {
"number": {
"type": "integer"
},
"text": {
"type": "string"
}
},
"required": ["number"],
"allOf": [
{
"not": {
"required": ["from"]
}
},
{
"not": {
"required": ["to"]
}
}
]
}
},
"type": "object",
"oneOf": [
{"$ref": "#/definitions/interval"},
{"$ref": "#/definitions/top"}
]
}

JsonSchema is not validated with oneOf

Need help to find the bug with this schema. It has oneOf operator.
Schema is here :
`{
"type": "object",
"required": [
"type",
"body"
],
"properties": {
"type": {
"description": "type of the document to post",
"type": "string",
"enum": [
"123",
"456"
]
},
"body": {
"type": "object",
"description": "body",
"oneOf": [{
"$ref": "#/definitions/abc",
"$ref": "#/definitions/def"
}]
}
},
"definitions": {
"abc": {
"type": "array",
"description": "abc",
"properties" : {
"name" : { "type" : "string" }
}
},
"def": {
"type": "array",
"description": "users","properties" : {
"name" : { "type" : "string" }
}
}
}
}`
My Json is this :
`{
"type": "123",
"body": {
"abc": [{
"name": "test"
}]
}
}`
It does not validate with tv4 and I also tried this online tool. It works without oneOf operator. Otherwise it does not validate it any tool.
Edit :
After reading the answers I modified the schema. New schema is :
{
"type": "object",
"properties": {
"type": {
"description": "type of the document to post",
"type": "string",
},
"body": {
"type": "object",
"description": "body",
"properties": {
"customers": {
"type": "array"
}
},
"anyOf": [
{
"title": "customers prop",
"properties": {
"customers": {
"type": "array",
"description": "customers",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
}
}
}
}
]
}
}
}
And json is here
{
"type": "customer",
"body": {
"none": [
{
"name": "test"
}
]
}
}
But it validates. I want to enforce one of "customers" or "users" in the body. To test I have removed users from the body.
Pl help.
The issue is that the data is passing both of your sub-schemas. oneOf means "match exactly one" - if you want "match at least one", then use anyOf.
In fact, both of your sub-schemas will pass all data. The reason is that properties is ignored when dealing with arrays.
What you presumably wanted to do instead is specify properties for the items in the array. For this, you need the items keyword:
"definitions": {
"abc": {
"type": "array",
"items": {
"type": "object",
"properties" : {
"name" : { "type" : "string" }
}
}
}
}
(You'll also need to add some distinct constraints - at the moment, both the "abc" and "def" definitions are identical apart from description, which makes the oneOf impossible because it will always match both or neither.)
Since you have the type at root level, you probably want the oneOf statement to check that an object with type "customer" has customers in the body (even though I would suggest skipping the body and placing customers and users directly in root object).
This works with your example, will require that an object with type "customer" has a body with "customers", and to clarify the matching, I let customer have the property "name" while the user has "username":
{
"type": "object",
"properties": {
"type": { "type": "string" },
"body": {
"type": "object",
"properties": {
"customers": {
"type": "array",
"items": { "$ref": "#/definitions/customer" }
},
"users": {
"type": "array",
"items": { "$ref": "#/definitions/user" }
}
}
}
},
"definitions": {
"customer": {
"type": "object",
"properties": { "name": { "type": "string" } },
"required": [ "name" ]
},
"user": {
"type": "object",
"properties": { "username": { "type": "string" } },
"required": [ "username" ]
}
},
"oneOf": [
{
"properties": {
"type": {
"pattern": "customer"
},
"body": {
"required": [ "customers" ]
}
}
},
{
"properties": {
"type": {
"pattern": "user"
},
"body": {
"required": [ "users" ]
}
}
}
]
}
When using "type": "array" then the item type is defined in the "items" property not "properties" property... Also both types in oneOf are same, but only one must match.
Try
...
"definitions": {
"abc": {
"type": "array",
"description": "abc",
"items" : {
"name" : { "type" : "string" }
}
},
"def": {
"type": "array",
"description": "users",
"items" : {
"username" : { "type" : "string" }
}
}
}