JsonSchema is not validated with oneOf - jsonschema

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" }
}
}
}

Related

Json Schema dependency on outer field

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" ]
}
}

JSON schema definition with multiple sets of enums for array attribute

I want to create a JSON schema for an object in which one of the attributes is restricted to multiple sets of enums.
For example:
{
"data": {
"type": "myObject",
"attributes": {
"states": [
"Washington",
"Oregon",
"California"
]
}
}
}
is a valid JSON object against the schema.
And
{
"data": {
"type": "myObject",
"attributes": {
"states": [
"British Columbia",
"Alberta",
"Ontario"
]
}
}
}
is also a valid JSON object agains the schema
BUT,
{
"data": {
"type": "myObject",
"attributes": {
"states": [
"Washington",
"Oregon",
"Alberta"
]
}
}
}
is NOT a valid JSON object against the schema.
I have tried the following schema definition:
{
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"type": {
"type": "string"
},
"attributes": {
"type": "object",
"properties": {
"states": {
"type": "array",
"items": {
"oneOf": [
{
"enum": ["Washington","Oregon","California"],
"description": "United States"
},
{
"enum": ["British Columbia","Alberta", "Ontario"],
"description": "Canada"
}
]
},
"description": "Filter by states"
}
}
}
}
}
}
}
But with this schema above this is still considered valid:
{
"data": {
"type": "myObject",
"attributes": {
"states": [
"Washington",
"Oregon",
"Alberta"
]
}
}
}
BTW, you can use this for testing whether a JSON object conforms to a schema: https://www.jsonschemavalidator.net/
Thank you!
You need to invert the order of the oneOf and the items keywords, so that the same oneOf clause is used for all items:
...
"states": {
"type": "array",
"oneOf": [
{
"items": {
"enum": ["Washington","Oregon","California"],
"description": "United States"
}
},
{
"items": {
"enum": ["British Columbia","Alberta", "Ontario"],
"description": "Canada"
}
}
]
},
...

how to set the type of a schema object based on the value of another property?

I have an object (from a 3rd party, so I can't change it) that have a property named "key", and another property called "value" that is optional, and it's type depends on the value of the "key" property.
For instance:
If the key is "comment", the type of value {"Text":"commentValue"}.
If the key is "offset", the type of value is {"seconds":int}.
If the key is "weather", the type of value is {"value": Enum["sun", "clouds", "rain"...]}
Moreover, some of the keys do not have the value property, so the schema should forbid it from appearing with these keys. one of these keys is "standby" (as you can see in my current attempt below)
I've tried manipulating the code samples from this SO answer, but couldn't make it work.
I'm currently attempting to validate output json against my schema attempts using Newtonsoft's JSON Schema Validator - but I can't seem to get the "value" property defined correctly.
This is my code so far:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "TestOptionalObject",
"type": "object",
"additionalProperties": false,
"required": [
"test"
],
"properties": {
"test": {
"$ref": "#/definitions/test"
}
},
"definitions": {
"test": {
"type": "object",
"additionalProperties": false,
"required": [
"key",
],
"properties": {
"key": {
"type": "string",
"enum": ["standby", "comment", "offset"]
},
"value" : {
"if": {
"properties": {
"key": {"enum": ["comment"]}
}
},
"then": {
"$ref": "#/definitions/commentValue"
},
"if": {
"properties": {
"key": {"enum": ["offset"]}
}
},
"then": {
"$ref": "#/definitions/offsetValue"
}
}
}
},
"commentValue" : {
"type": "object",
"additionalProperties": false,
"required": [
"text",
],
"properties": {
"text" : {"type" : "string"}
}
},
"offsetValue" : {
"type": "object",
"additionalProperties": false,
"required": [
"seconds",
],
"properties": {
"seconds" : {
"type": "integer",
"format": "int32"
}
}
}
}
}
And this is the error messages I get:
JSON does not match schema from 'then'.
Schema path: #/definitions/offsetValue/then
Property 'text' has not been defined and the schema does not allow additional properties.
Schema path: #/definitions/offsetValue/additionalProperties
Required properties are missing from object: seconds.
Schema path: #/definitions/offsetValue/required
Json examples to validate:
Should fail:
{
"test": {
"key": "comment",
"value": {"seconds":12}
}
}
{
"test": {
"key": "standby",
"value": {"asdf":12}
}
}
Should pass:
{
"test": {
"key": "comment",
"value": {"text":"comment text"}
}
}
{
"test": {
"key": "offset",
"value": {"seconds":12}
}
}
I have changed your JSON Schema so it does what you expect, apart form key of standby as you didn't include that in your schema, and you should be able to replicate the pattern I've created to add new keys as required.
The major issue you had was a false assumption about where to place if/then/else keywords. They are applicator keywords, and so must be applied to the object which you are checking the condition of, and not a properties key value. Because you were using if/then/else in the object which was a value of value, you were applying if/then/else to the value of value rather than test.
You needed your if to apply to test to get the correct scope for checking the key property value.
Here is the resulting fixed schema:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "TestOptionalObject",
"type": "object",
"additionalProperties": false,
"required": [
"test"
],
"properties": {
"test": {
"$ref": "#/definitions/test"
}
},
"definitions": {
"test": {
"type": "object",
"required": [
"key"
],
"properties": {
"key": {
"type": "string",
"enum": [
"standby",
"comment",
"offset"
]
}
},
"allOf": [
{
"if": {
"properties": {
"key": {
"const": "comment"
}
}
},
"then": {
"properties": {
"value": {
"$ref": "#/definitions/commentValue"
}
}
}
},
{
"if": {
"properties": {
"key": {
"const": "offset"
}
}
},
"then": {
"properties": {
"value": {
"$ref": "#/definitions/offsetValue"
}
}
}
}
]
},
"commentValue": {
"type": "object",
"additionalProperties": false,
"required": [
"text"
],
"properties": {
"text": {
"type": "string"
}
}
},
"offsetValue": {
"type": "object",
"additionalProperties": false,
"required": [
"seconds"
],
"properties": {
"seconds": {
"type": "integer",
"format": "int32"
}
}
}
}
}
If you want any more help, please feel free to join the JSON Schema slack using the discussion link on the http://json-schema.org site.

Using a json schema in multiple layouts

I'm helping to build an interface that works with Json Schema, and I have a question about interface generation based on that schema. There are two display types - one for internal users and one for external users. Both are dealing with the same data, but the external users should see a smaller subset of fields than the internal users.
For example, here is one schema, it defines an obituary:
{
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "",
"type": "object",
"required": [
"id",
"deceased"
],
"properties": {
"id": { "type": "string" },
"account": {
"type": "object",
"required": [
"name"
],
"properties": {
"id": { "type": "number" },
"name": { "type": "string" },
"website": {
"anyOf": [
{
"type": "string",
"format": "uri"
},
{
"type": "string",
"maxLength": 0
}
]
},
"email": {
"anyOf": [
{
"type": "string",
"format": "email"
},
{
"type": "string",
"maxLength": 0
}
]
},
"address": {
"type": "object",
"properties": {
"address1": { "type": "string" },
"address2": { "type": "string" },
"city": { "type": "string" },
"state": { "type": "string" },
"postalCode": { "type": "string" },
"country": { "type": "string" }
}
},
"phoneNumber": {
"anyOf": [
{
"type": "string",
"format": "phone"
},
{
"type": "string",
"maxLength": 0
}
]
},
"faxNumber": {
"anyOf": [
{
"type": "string",
"format": "phone"
},
{
"type": "string",
"maxLength": 0
}
]
},
"type": { "type": "string" }
}
},
"deceased": {
"type": "object",
"required": [
"fullName"
],
"properties": {
"fullName": { "type": "string" },
"prefix": { "type": "string" },
"firstName": { "type": "string" },
"middleName": { "type": "string" },
"nickName": { "type": "string" },
"lastName1": { "type": "string" },
"lastName2": { "type": "string" },
"maidenName": { "type": "string" },
"suffix": { "type": "string" }
}
},
"description": { "type": "string" },
"photos": {
"type": "array",
"items": { "type": "string" }
}
}
}
Internal users would be able to access all the fields, but external users shouldn't be able to read/write the account fields.
Should I make a second schema for the external users, or is there a way to indicate different display levels or public/private on each field?
You cannot restrict acess to the fields defined in a schema, but you can have 2 schema files, one defining the "public" fields, and the other one defining the restricted fields plus including the restricted fields.
So
public-schema.json:
{
"properties" : {
"id" : ...
}
}
restricted-schema.json:
{
"allOf" : [
{
"$ref" : "./public-schema.json"
},
{
"properties" : {
"account": ...
}
}
]
}

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"}
]
}