json schema - field is required based on another field value - jsonschema

I'm working on creating a JsonSchema(v4).
I'm trying to make one property required based off the value of another property from it's parent.
Parent
User
subtype
address
Child
Address
line1
line2
companyName (required if user subtype is company)
How could this be done?
I have something like this now...
{
"User": {
"title": "User",
"type": "object",
"id": "#User",
"properties": {
"subtype": {
"type": "string"
},
"address": {
"$ref": "Address"
}
}
}
"Address": {
"title": "Address",
"type": "object",
"id": "#Address",
"properties": {
"line1": {
"type": "string"
},
"line2": {
"type": "string"
},
"companyName": {
"type": "string"
}
},
"required": ["line1", "line2"]
}
}
Subtype is a arbitrary string, so a full list of the different subtypes is not possible.

Add this to your User schema. Essentially it reads as: either "subtype" is not "company" or "address" requires "companyName".
"anyOf": [
{
"not": {
"properties": {
"subtype": { "enum": ["company"] }
}
}
},
{
"properties": {
"address": {
"required": ["companyName"]
}
}
}
]

Related

How to properly define an object within definition and reference in multiple places for Json Schema

I am trying to create a json schema wherein I have an object within a definition & this definition is called within multiple places.
I see an error saying UnhandledPromiseRejectionWarning: Error: duplicate type name: Location
I have the below code.
{
"$schema": "http://json-schema.org/draft-07/schema",
"definitions": {
"holiday": {
"description": "A collection of time off associated with the employee.",
"required": [],
"properties": {
"location": {
"type": "object",
"nullable": true,
"title": "Location",
"properties": {
"city": {
"type": "string",
"nullable": true,
"tsType": "string | null",
"description": ""
}
}
}
},
"type": "object"
}
},
"description": "The model for the employee object received on the Ingress API.",
"properties": {
"eventType": {
"avroType": "string",
"enum": ["EMPLOYEE_TIMEOFF_CREATED", "EMPLOYEE_CREATED_OR_UPDATED"],
"tsEnumNames": ["EmployeeTimeOffCreated", "EmployeeCreatedOrUpdated"],
"type": "string"
},
"employeeCreatedOrUpdated": {
"description": "Event data for a employee create request.",
"required": ["code", "firstName", "lastName"],
"properties": {
"code": {
"description": "A unique code for an employee.",
"minLength": 1,
"type": "string"
},
"firstName": {
"description": "This field describes the first name of the employee.",
"minLength": 1,
"type": "string"
},
"middleName": {
"description": "This field describes the middle name of the employee.",
"nullable": true,
"tsType": "string | null",
"type": "string"
},
"lastName": {
"description": "This field describes the last name of the employee.",
"minLength": 1,
"type": "string"
},
"timeOff": {
"description": "A collection of employee time off associated with the employee.",
"items": {
"$ref": "#/definitions/holiday"
},
"nullable": true,
"type": "array"
}
},
"title": "EmployeeCreatedOrUpdated",
"type": "object"
},
"employeeTimeOffCreated": {
"description": "Event data for an employee time off created request.",
"required": ["timeOffCreated", "employeeCode"],
"$id": "https://io.something/v2/employee/employeeTimeOffCreated",
"properties": {
"timeOffCreated": {
"$ref": "#/definitions/holiday"
},
"employeeCode": {
"description": "A unique code for an employee.",
"minLength": 1,
"type": "string"
}
},
"title": "EmployeeTimeOffCreated",
"type": "object"
}
},
"required": ["eventType"],
"title": "EmployeeEvent",
"type": "object"
}
So, I use the holiday definition at two places. I tried to have an id within $ref but that doesn't work. Any help is highly appreciated. Thanks.
Here's your schema reduced to only the parts needed to understand the problem.
{
"$schema": "http://json-schema.org/draft-07/schema",
"type": "object",
"properties": {
"employeeCreatedOrUpdated": {
"type": "object",
"properties": {
"timeOff": {
"type": "array",
"items": { "$ref": "#/definitions/holiday" }
}
}
},
"employeeTimeOffCreated": {
"$id": "https://io.something.ingress/v2/employee/employeeTimeOffCreated",
"type": "object",
"properties": {
"timeOffCreated": { "$ref": "#/definitions/holiday" }
}
}
}
"definitions": {
"holiday": true
}
}
When you use $id in a sub-schema like this, it indicates that the sub-schema is a completely separate schema embedded in the parent schema. Any references inside of the embedded schema are relative the the $id, not the parent schema. So, the reference at "timeOffCreated" expects #/definitions/holiday relative to the embedded schema. There's nothing there, so you get an error.
If you don't need "employeeTimeOffCreated" to be an embedded schema, the easiest thing is to remove $id and your references will work. Otherwise, you can give /definitions/holiday an $id as well and reference with that URI instead.
Embedded schemas are really only good for bundling schemas for distribution. Otherwise, you probably want to maintain separate schemas for each of your entities and just reference other schemas when you need them.
Here's what it would look like neatly bundled. If you want to work on these as separate schemas as suggested, you just need to extract each of the definitions into their own schema.
{
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "https://io.something.ingress/v2/employee",
"type": "object",
"properties": {
"employeeCreatedOrUpdated": {
"type": "object",
"properties": {
"timeOff": {
"type": "array",
"items": { "$ref": "./holiday" }
}
}
},
"employeeTimeOffCreated": { "$ref": "./employeeTimeOffCreated" }
}
"definitions": {
"employeeTimeOffCreated": {
"$id": "./employeeTimeOffCreated",
"type": "object",
"properties": {
"timeOffCreated": { "$ref": "./holiday" }
}
},
"holiday": {
"$id": "./holiday",
...
}
}
}
Additional reference: https://json-schema.org/understanding-json-schema/structuring.html#bundling

Referring inner properties in the IF condition - JSON Schema

The following is a sample schema to depict the issue
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"definitions": {
"person": {
"type": "object",
"properties": {
"age": {
"type": "string"
}
}
}
},
"properties": {
"child": {
"$ref": "#/definitions/person"
}
},
"required": [
"child"
],
"if": {
"properties": {
"person/age": {
"const": "3"
}
}
},
"then": {
"properties": {
"guardian": {
"$ref": "#/definitions/person"
}
},
"required": [
"guardian"
]
}
}
Is there a way to refer age inside the person object?
{"child":{"age":"3"}}. Should fail as guardian tag is missing
The above data should fail as the guardian object is missing.
Remember that if is just a regular schema validating against the instance. Just nest your properties like you would with any nested object structure.
{
"type": "object",
"properties": {
"child": {
"type": "object",
"properties": {
"age": { "const": "3" }
},
"required": ["age"]
}
},
"required": ["child"]
}
Note that the type and required keywords are necessary to not inadvertently trigger the then schema. For example, with out them, these would cause the then to trigger when you probably didn't want it to.
{}
{ "child": null }
{ "child": {} }

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

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