Json Schema Validation of Array or Objects - jsonschema

I have a set of options for validation:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "parsePosition Validator",
"type": "object",
"properties": {
"option1": {
"type": "array",
"items": {
"tpye": "number"
},
"minItems": 3
},
"option2": {
"type": "array",
"items": {
"tpye": "number"
},
"minItems": 3
},
"option3": {
"oneOf": [
{
"type": "array",
"items": {
"tpye": "number"
},
"minItems": 3
},
{
"x": { "type":"number" },
"y": { "type":"number" },
"z": { "type":"number" },
"required": ["x","y","z"]
}
}
},
"oneOf": [
{ "required":["option1"] },
{ "required":["option2"] },
{ "required":["option3"] }
]
}
Essentially the input of the json can be any of the three options. My issue arises when trying to validate option3. Possible inputs could be:
{
"option3": [1,2,3]
}
or
{
"option3": {
"x": 1,
"y": 2,
"z": 3
}
}
However, when I use the array rather than objects, the validator does not throw an issue with an empty array or an array of incorrect size.
Am I missing something?

You are missing something very simple...
You haven't put type: object in your second subschema in the oneOf. for option 3.
The reason you need this is because JSON Schema is a constraints based language.
Specifying properties, doesn't mean the the instance location HAS to be an object; You also need to say "this location should be an object".
An array of any size is valid under your subschema oneOf[1], because properties and `required are only applicable to an object.

Related

Make a property required depending on a query parameter with OpenAPI and JSON-Schema

I have a REST API that support for most calls a with query parameter, allowing the user to specify which additional fields they want to load along with the resource’s basic data.
I can describe the query parameter with OpenAPI, and I can describe the format of the response with JSON-Schema. But how can I describe the relation between the parameter and the response?
What I want to describe is:
/api/resource/{id} returns:
{"id": 123, … }
/api/resource/{id}?with=stuff returns:
{"id": 123, …, "stuff": { … }}
I would like this stuff property to be defined as required. Is there a way of doing this?
(What makes this probably even more impossible is that the value of with can be a comma-separated list of properties, but at this point I’m not hoping)
You can't do anything in JSON Schema using out of context data. So, the only way to do what you want is to put the query data into your response.
{
"id": 123,
"query": {},
...
}
{
"id": 123,
"with": ["stuff"],
...
"stuff": { ... }
}
With pure JSON Schema or OpenAPI 3.1, you can use if/then and contains to express the conditional requirement.
{
"type": "object",
"properties": {
"id": { "type": "object" },
"with": { "type": "array", "items": { "type": "string" } },
...
"stuff": { ... }
},
"required": ["id"],
"allOf": [
{
"if": {
"properties": {
"with": { "contains": { "const": "stuff" } }
},
"required": ["with"]
},
"then": { "required": ["stuff"] }
},
... additional conditionals ...
]
}
However, assuming you are on OpenAPI 3.0, you don't have if, then, or contains. It's still possible, but it gets a bit ugly because you need to use a bunch of confusing boolean logic concepts instead.
{
"type": "object",
"properties": {
"id": { "type": "object" },
"with": { "type": "array", "items": { "type": "string" } },
...
"stuff": { ... }
},
"required": ["id"],
"allOf": [
{
"anyOf": [
{
"not": {
"properties": {
"with": {
"not": {
"items": { "not": { "enum": ["stuff"] } }
}
}
},
"required": ["with"]
}
},
{ "required": ["stuff"] }
]
},
... additional conditionals ...
]
}
JSON schema: query string required
schema: {
tags: ["sample"],
summary: "Set query string values to required",
querystring: {
type: "object",
properties: {
name: {
type: "string",
},
address: {
type: "string",
},
},
required: ["name"],
},
},

Json Schema Different required properties for first element in array?

Suppose I want to validate an array in json where only the first element has an optional property. The rest of the items in the array would required the property.
schema.json
{
"type": "object",
"properties": {
"x": {
"type": "array",
"items": {
"oneOf": [
{ "$ref": "./first.json" },
{ "$ref": "./rest.json" }
]
}
}
}
}
first.json
{
"type": "object",
"properties": {
"y": { "type": "number" },
"z": { "type": "boolean" }
},
"required": ["y"]
}
rest.json
{
"type": "object",
"properties": {
"y": { "type": "number" },
"z": { "type": "boolean" }
}
}
Valid json:
{
"x": [
{ "z": true },
{ "y": 4, "z": true },
{ "y": 5 }
]
}
Invalid json:
{
"x": [
{ "z": true },
{ "z": true }, // this line would be invalid
{ "y": 5 }
]
}
However, the above setup gives me "multiple schemas match" when I provide the y value. Another approach could be:
schema.json
{
"type": "object",
"properties": {
"x": {
"type": "array",
"items": [
{ "$ref": "./first.json" },
{ "$ref": "./rest.json" }
]
}
}
}
This schema does what I want for the first two elements in the array (not requiring y in the first but yes in the second), but does not apply any schema to the objects after the second.
The items keyword has two forms. The first is the schema-form that you are using. The second is the array-form. The array-form takes an array of schemas where each schema applies to it's corresponding item in the instance. For example, when given an array with two schema, it validates the first item in the instance against the first schema and the second item against the second schema.
There is also an additionalItems keyword that works with the array-form of items. Any item that doesn't correspond to a schema in items is validated against the additionalItems schema. Using the previous example with two schemas in items, an instance with three items would have it's third item evaluated against the additionalItems schema.
So, we can use the array-form of items to apply "./first.json" to the first items and use additionalItems to apply "./rest.json" to the rest of the array.
{
"type": "object",
"properties": {
"x": {
"type": "array",
"items": [{ "$ref": "./first.json" }],
"additionalItems": { "$ref": "./rest.json" }
}
}
}

JSON Schema switch object properties based on enum

I've been struggling with "switch" in JSON Schema. Went through couple of GitHub and SO discussions on this topic but haven't find solution.
My intention is to vary "payload" object properties based on "id" enum that will have 30 different mappings ("payload" definitions per enum "id").
For example first message json object will have amount and other properties but for the demo purpose let's go only with one property (amout):
{
"message": {
"id": 1,
"correlationId": "a0011e83-280e-4085-b0f1-691059aaae61",
"payload": {
"amount": 100
}
}
}
And second json:
{
"message": {
"id": 2,
"correlationId": "a0011e83-280e-4085-b0f1-691059aaae61",
"payload": {
"code": "xyz"
}
}
}
Is there a way to build JSON Schema (draft 7 or any other) in this manner?
What you're asking for is a fairly common requirement. Using oneOf/anyOf should get you where you want.
In those cases where the alternatives are mutually exclusive (due to the different "id" values), I'm in favour of anyOf to allow Schema Validator to stop checking when encountering the first matching subschema – whereas oneOf implies that all other alternatives must not match, e.g. in case of "id": 1 a validator would only have to check against the first subschema in an anyOf to indicate that it is valid while for oneOf it'd have to check against the other 29 to ensure that those aren't also valid. But you may find oneOf more expressive for human consumers of your schema.
For your particular scenario, I'd imagine something along the lines of the following schema:
{
"type": "object",
"required": ["message"],
"properties": {
"message": {
"type": "object",
"required": ["id", "correlationId", "payload"],
"properties": {
"id": { "enum": [1, 2, 3] },
"correlationId": { "type": "string" },
"payload": { "type": "object" }
},
"anyOf": [
{
"properties": {
"id": { "const": 1 },
"payload": { "$ref": "#/definitions/payload1" }
}
},
{
"properties": {
"id": { "const": 2 },
"payload": { "$ref": "#/definitions/payload2" }
}
},
{
"properties": {
"id": { "const": 3 },
"payload": { "$ref": "#/definitions/payload3" }
}
},
]
}
},
"definitions": {
"payload1": {
"type": "object",
"required": ["amount"],
"properties": {
"amount": { "type": "integer" }
}
},
"payload2": {
"type": "object",
"required": ["code"],
"properties": {
"code": { "type": "string" }
}
},
"payload3": {
"type": "object",
"required": ["foo"],
"properties": {
"foo": { "type": "string" }
}
}
}
}

JSON SCHEMA - How can i check a values exists in an array

I have a simple question. I have a property groupBy which is an array and contain only two possible values "product" and "date". Now i want to make another property required based upon a value exists in the groupBy array. In this case when my groupBy array contains "date" i want to make resolution required! How can i do that ?
Who can i check if an array contains a value ?
var data = {
"pcsStreamId": 123123,
"start": moment().valueOf(),
"end": moment().valueOf(),
"groupBy" : ["product"]
};
var schema = {
"type": "object",
"properties": {
"pcsStreamId": { "type": "number" },
"start": { "type": "integer", "minimum" : 0 },
"end": { "type": "integer", "minimum" : 0 },
"groupBy": {
"type": "array",
"uniqueItems": true,
"items" : {
"type": "string",
"enum": ["product", "date"]
},
"oneOf": [
{
"contains": { "enum": ["date"] },
"required": ["resolution"]
}
]
},
"resolution" : {
"type": "string",
"enum": ["day", "year", "month", "shift"]
},
},
"required": ["pcsStreamId", "start", "end", "groupBy"]
};
To solve the problem we have to use a boolean logic concept called implication. To put the requirement in boolean logic terms, we would say "groupBy" contains "date" implies that "resolution" is required. Implication can be expressed as "(not A) or B". In other words, either "groupBy" does not contain "date", or "resolution" is required. In this form, it should be more clear how to implement the solution.
{
"type": "object",
"properties": {
"pcsStreamId": { "type": "number" },
"start": { "type": "integer", "minimum": 0 },
"end": { "type": "integer", "minimum": 0 },
"groupBy": {
"type": "array",
"uniqueItems": true,
"items": { "enum": ["product", "date"] }
},
"resolution": { "enum": ["day", "year", "month", "shift"] }
},
"required": ["pcsStreamId", "start", "end", "groupBy"],
"anyOf": [
{ "not": { "$ref": "#/definitions/contains-date" } },
{ "required": ["resolution"] }
],
"definitions": {
"contains-date": {
"properties": {
"groupBy": {
"contains": { "enum": ["date"] }
}
}
}
}
}
Edit
This answer uses the new draft-06 contains keyword. I used it because the questioner used it, but if you are on draft-04, you can use this definition of "contains-date" instead. It uses another logic identity (∃x A <=> ¬∀x ¬A) to get the functionality of the contains keyword.
{
"definitions": {
"contains-date": {
"properties": {
"groupBy": {
"not": {
"items": {
"not": { "enum": ["date"] }
}
}
}
}
}
}
}
Your groupby property seems to be an object. May be its the nested enum property that you are talking about ( since that is an array and contains date and product). However, once you parsed this json to an object you can check something like:
groupby.items.enums.indexOf('date') === -1
array.indexOf(anyItem) returns the index of the item, if present in the array else it returns -1.
Hope that helps

json schema object property constraints

In my schema I have an array of phone objects. Each object has a "status" property, which can be one of three values: "Primary", "Active" and "Not-in-use".
I want to set the following constraint:
If the number of phone objects > 0 then exactly one must have status="Primary"
Is this possible with json schema? If so, how?
This schema is pretty close to what you want. The only restriction is that the "Primary" phone number needs to be the first item in the array.
You might be able to get "Primary" to be anywhere in the array with some creative use of not. I'll update the answer if I figure it out.
{
"type": "object",
"properties": {
"phoneNumbers": {
"type": "array",
"items": [{ "$ref": "#/definitions/primaryPhone" }],
"additionalItems": { "$ref": "#/definitions/additionalPhone" }
}
},
"definitions": {
"phone": {
"type": "object",
"properties": {
"label": { "type": "string" },
"number": { "type": "string" }
},
"required": ["label", "number", "status"]
},
"primaryPhone": {
"allOf": [{ "$ref": "#/definitions/phone" }],
"properties": {
"status": { "enum": ["Primary"] }
}
},
"additionalPhone": {
"allOf": [{ "$ref": "#/definitions/phone" }],
"properties": {
"status": { "enum": ["Active", "Not-in-use"] }
}
}
}
}