JSON schema definition with multiple sets of enums for array attribute - jsonschema

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

Related

Json property structure dependant of another property

I've been working on a json schema to validate the answers from one of my webservices.
The answer is splitted in two properties: data and status. If status.code is set to 0, then data will have to respect a specific schema. Else, if status.code is set to -1, data won't be read, so I don't want to check if it respects the schema.
Here is the schema :
{
"$schema": "http://json-schema.org/schema#",
"id": "file://registration.json",
"type": "object",
"properties": {
"status": {
"$ref": "#/definitions/classes/status"
}
},
"anyOf": [
{
"$ref": "#/definitions/conditions/status-is-ok"
},
{
"$ref": "#/definitions/conditions/status-is-nok"
}
],
"definitions": {
"classes": {
"status": {
"type": "object",
"properties": {
"code": {
"type": "integer"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
]
},
"data": {
"type": "object",
"properties": {
"propertyA": {
"type": "#/definitions/classes/metadatauser"
},
"propertyB": {
"type": "#/definitions/classes/membreinfo"
}
},
"required": ["propertyA", "propertyB"]
}
},
"conditions": {
"status-is-ok": {
"status": {
"properties": {
"code": 0
}
},
"data": {
"$ref": "#/definitions/classes/data"
}
},
"status-is-nok": {
"status": {
"properties": {
"code": -1
}
},
"data": {
"type": "object"
}
}
}
}
}
And here's an example of what should not be validated:
{
"data": {},
"status": {
"code": 0,
"message": "OK"
}
}
At the moment, this portion of code passes, and I don't know why.
You've got a few things wrong here, so I'll try to explain all of them. You were on the right track!
"properties": {
"code": 0
}
The value of "properties" MUST be an object. Each value of this object
MUST be a valid JSON Schema.
http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.5.4
You can't put the value you expect as the value for a property key.
You CAN however use the [const]1 keyword to achive a specific value validation.
"$ref": "#/definitions/conditions/status-is-ok"
...
"conditions": {
"status-is-ok": {
"status": {
"properties": {
[The definitions] keyword's value MUST be an object. Each member value of this
object MUST be a valid JSON Schema.
https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-validation-01#section-9
This means that you need to treat each value of each key in a defintions as a JSON Schema. If you had a JSON Schema where you did not nest "status" inside a properties object, no validation would take place. The same is true for "data".
(Strictly, according to the definitions section of the spec, you MUST NOT nest schemas deeply in the definitions object, but this seems to be supported by some implementations anyway, and resolves using the correct resolution rules. Prefixing may be better.)
The complete fixed schema is as follows.
{
"$schema": "http://json-schema.org/schema#",
"id": "file://registration.json",
"type": "object",
"properties": {
"status": {
"$ref": "#/definitions/classes/status"
}
},
"anyOf": [
{
"$ref": "#/definitions/conditions/status-is-ok"
},
{
"$ref": "#/definitions/conditions/status-is-nok"
}
],
"definitions": {
"classes": {
"status": {
"type": "object",
"properties": {
"code": {
"type": "integer"
},
"message": {
"type": "string"
}
},
"required": [
"code",
"message"
]
},
"data": {
"type": "object",
"properties": {
},
"required": [
"propertyA",
"propertyB"
]
}
},
"conditions": {
"status-is-ok": {
"properties": {
"status": {
"properties": {
"code": {
"const": 0
}
}
},
"data": {
"$ref": "#/definitions/classes/data"
},
},
"additionalProperties": false
},
"status-is-nok": {
"properties": {
"status": {
"properties": {
"code": {
"const": -1
}
}
},
"data": {
"type": "object"
},
},
"additionalProperties": false
}
}
}
}
Please do let me know if any of this doesn't make sense.
Feel free to join the JSON Schema slack server should you want to discuss any aspects further! Happy to also comment here.

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

TypeError: datastore.getRootCollection is not a function

I assumed a dstore that was created using dmodel was still a dstore and inherited all the dstore functionality. So there's a method listed called getRootCollection but when i try to run this method on the store it fails with an error (no such function)
Here is my code
<script>
require(
[
'dojo/_base/declare',
'dstore/Memory',
'dmodel/extensions/jsonSchema',
'dmodel/validators/StringValidator',
'dmodel/store/Validating',
"dmodel/Model"
],
function (declare, Memory, jsonSchema, StringValidator, Validating, Model) {
var vMem = (declare([Memory, Validating]))({
Model: jsonSchema(
{
"$schema": "http://json-schema.org/draft-04/schema",
"description": "my schema",
"type": "object",
"properties": {
"page": {
"type": "object",
"properties": {
"detailsCanvas": {
"description": "test value",
"type": "object",
"$ref": "#/definitions/details"
}
}
},
"elements": {
"type": "array",
"items": {
"title": "Element",
"type": "object",
"properties": {
"id": {
"type": "string"
},
"positionX": {
"description": "The X coordinate",
"type": "number"
},
"elementSpecificProperties": {
"type": "object",
"oneOf": [
{ "$ref": "#/definitions/label" }
]
}
}
}
}
},
"definitions": {
"details": {
"type": "object",
"properties": {
"height": {
"type": "string"
}
}
},
"label": {
"type": "object",
"properties": {
"value": {
"type": "string"
}
}
}
}
}
)
});
vMem.setData(
{
"page": {
"detailsCanvas": {
"height": "100px"
}
},
"elements": [
{
"id": "1",
"positionX": 20,
"elementSpecificProperties": {
"value": "value_1"
}
},
{
"id": "2",
"positionX": 5,
"elementSpecificProperties": {
"value": "value_2"
}
}
]
});
var blah = vMem.getRootCollection(); //type error - getRootCollection is not a function
});
</script>
getRootCollection() is part of the Tree model of dstore. You only mixin the dstore/Memory. So that is why you get that error. Checkout the documentation of dstore for more information.

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