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

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

Related

Add additional properties to json array member schema

I'm trying to create 2 separate schemas for some json files which serve as scripts.
One schema should contain all commands possible, while another one should only contain a subset of commands.
My thinking was I create minimal schema first:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"$schema": {},
"commands": {
"type": "array",
"items":
{
"title": "One step of the migration",
"type": "object",
"minProperties": 1,
"maxProperties": 1,
"properties": {
"simple_command_1": {"type": "object"},
"simple_command_2": {"type": "object"},
}
}
}
}
}
which would validate json files like this:
{
"$schema": "../../migration_schema_v1.json",
"commands": [
{"simple_command_1": {}},
{"simple_command_1": {}},
{"simple_command_2": {}},
{"simple_command_2": {}},
]
I'm trying to avoid using the word extend but... I want to be able to create a second schema that has every command the first one has and an additional advanced_command_3. So I created this:
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$ref": "/migration_schema_v1.json#/",
"type": "object",
"properties": {
"foo": {
"type": "object",
"title": "this does work"
},
"commands": {
"items":
{
"properties": {
"advanced_command_3": {"type": "object"}
}
}
}
}
}
I'm able to refer this second schema, and it does inherit everything from the first one, but I'm not really able to add any properties (commands)
I was however able to add additional properties in the root, next to "commands". See "foo" property.
Using allOf[] or $ref in the sub-property doesn't seem to make a difference.
Am I getting this all wrong?
Thanks!
I would actually break this into three schemas. The first contains the primary definition for your data, including all of the valid commands.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/definitions",
"type": "object",
"properties": {
"$schema": {},
"commands": {
"type": "array",
"items":
{
"title": "One step of the migration",
"type": "object",
"minProperties": 1,
"maxProperties": 1,
"properties": {
"simple_command_1": {"type": "object"},
"simple_command_2": {"type": "object"},
"advanced_command_3": {"type": "object"}
}
}
}
}
}
The other two are your simple and advanced schemas. They work by defining that only the possibilities of the commands you want are required then referencing back to the definition schema to get the rest of the requirements.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/simple",
"$ref": "definitions",
"properties": {
"commands": {
"items": {
"anyOf": [
{ "required": ["simple_command_1"] },
{ "required": ["simple_command_2"] }
]
}
}
}
}
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/advanced",
"$ref": "definitions",
"properties": {
"commands": {
"items": {
"anyOf": [
{ "required": ["simple_command_1"] },
{ "required": ["simple_command_2"] },
{ "required": ["advanced_command_3"] }
]
}
}
}
}
I think this setup contains minimal repetition.
At this point you might be able to get away with what you're wanting to do with the extends thing. You'd have to extract the commands requirements in the simple schema into a $defs keyword and reference them from the advanced schema.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/simple",
"$defs": {
"simpleCommands": {
"anyOf": [
{ "required": ["simple_command_1"] },
{ "required": ["simple_command_2"] }
]
}
},
"$ref": "definitions",
"type": "object",
"properties": {
"commands": {
"items": { "$ref": "#/$defs/simpleCommands" }
}
}
}
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/advanced",
"$ref": "definitions",
"type": "object",
"properties": {
"commands": {
"items": {
"anyOf": [
{ "$ref" : "simple#/$defs/simpleCommands" },
{ "required": ["advanced_command_3"] }
]
}
}
}
}
I haven't tested this though.

Cannot find classification schema reference in JSON Schema

I have a product schema which tries to reference an id in my document. It is a common reference to multiple objects. Unfortunately, my ide claims classification reference cannot be found. I am very new to json schemas and find only snippets which don't quite show how the references are supposed to work. Here is my schema.
{
"$schema": "https://json-schema.org/draft-07/schema#",
"$id": "https://digital.com/schemas/products",
"description": "Schema for Product Data",
"title": "Products",
"type": "object",
"required": ["products"],
"properties": {
"products": {
"type": "array"
},
"options": {
"type": "array",
"items": {
"type": "object",
"properties": {
"productId": {
"type": "string"
},
"productName": {
"type": "string"
},
"categories": {
"type": "object",
"additionalProperties": false,
"allOf": [
{ "$ref":"/products/classification" }
]
},
"productType": {
"type": "string",
"enum": ["electronic", "digital", "internet", "video"]
}
},
"required": ["productId"]
}
},
"classification": {
"$id": "/products/classification",
"type":"object",
"properties": {
"relevance-score": {
"type":"integer",
"minimum": 1,
"maximum": 5
},
"group":{
"enum":["adult","teen","seniors"]
}
}
},
"definitions": {
"mp4": {
"type": "object",
"properties": {
"mediaType": {
"type": "string",
"enum": ["video"]
},
"playlength": {
"type": "integer"
}
}
}
},
"mp3": {
"type": "object",
"properties": {
"mediaType": {
"enum": ["audio"]
}
}
}
}
}
I have defined a classification object to use in the class property of categories object like this
"$id":"/products/classification".
I tried setting it relative to the $id at the top of the document which is "https://digital.com/schemas/products" but I'm sure I haven't set it correctly. I want to use the classification object in the allOf property of the categories object.
Your reference is "$ref": "/products/classification". Where is this supposed to point?
Odds are the validator you're using is looking for a schema with the $id of "https://digital.com/products/classification". If the validator doesn't know about this schema (or perhaps is searching for that file), it can't get to it.
The other possibility is that you intend this to be a JSON Pointer. If that's the case, it needs to be URI-formatted: "#/products/classification"
However, this location doesn't existin within your schema, so it'll need to be updated.

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 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 dynamic key validation

Facing an issue with schema validation.
schema :
{
"type": "object",
"$schema": "http://json-schema.org/draft-03/schema",
"id": "#",
"required": true,
"patternProperties": {
"^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,6}$": {
"type": "object",
"required": true,
"properties": {
"_from": {
"id": "_from",
"type": "string",
"required": true
},
"message": {
"type": "object",
"id": "message",
"properties": {
"detail": {
"type": "string",
"id": "detail",
"required": true
},
"from": {
"type": "string",
"id": "from",
"required": true
}
}
}
}
}
}
}
json :
{
"tom#example.com": {
"_from": "giles#gmail.com",
"message": {
"from": "Giles#gmail.com",
"detail": "AnyonewanttomeetmeinParis"
}
},
"harry#example.com": {
"_from": "giles#gmail.com",
"message": {
"from": "Giles#gmail.com",
"detail": "AnyonewanttomeetmeinParis"
}
}
}
Here the key email address is dynamic, somehow it doesn't validate regex for email validation.
Can you please advise me to correct the schema.
I am validating using : http://json-schema-validator.herokuapp.com/index.jsp
I see in your pattern that you seem to have forgotten to escape some characters or didn't do it correctly:
"^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,6}$"
and it causes the error that you can see when you hover the mouse over the link at the top of the validator:
it should be:
"^[A-Z0-9\\._%\\+-]+#[A-Z0-9\\.-]+\\.[A-Z]{2,6}$"
or without escaping the inner/class characters but I'd use the first pattern because I think its intention is clearer:
"^[A-Z0-9._%+-]+#[A-Z0-9.-]+\\.[A-Z]{2,6}$"
You need to have two \ because the first \ is an escape for the second \. With a single one it wouldn't work because there is no escape sequence like \. or \+ in javascript. You want to have a \in the pattern itself.
However json schema patternProperties are case sensitive by default so you need to extend your email pattern by adding a-z to it:
"^[A-Za-z0-9\\._%\\+-]+#[A-Za-z0-9\\.-]+\\.[A-Za-z]{2,6}$"
(I didn't find any other way to make it case insensitive)
You also need to exclude any other property names by adding "additionalProperties": false next to the patternProperties or otherwise it catches everything else that does not match the pattern.
The working schema should then look like this:
{
"type": "object",
"$schema": "http://json-schema.org/draft-03/schema",
"id": "#",
"required": true,
"patternProperties": {
"^[A-Za-z0-9\\._%\\+-]+#[A-Za-z0-9\\.-]+\\.[A-Za-z]{2,6}$": {
"type": "object",
"required": true,
"properties": {
"_from": {
"id": "_from",
"type": "string",
"required": true
},
"message": {
"type": "object",
"id": "message",
"properties": {
"detail": {
"type": "string",
"id": "detail",
"required": true
},
"from": {
"type": "string",
"id": "from",
"required": true
}
}
}
}
}
},
"additionalProperties": false
}
I've tested it on: http://jsonschemalint.com/
Changed the schema as per draft 04 :
{
"type": "object",
"$schema": "http://json-schema.org/draft-04/schema",
"patternProperties": {
"^[A-Za-z0-9\\._%\\+-]+#[A-Za-z0-9\\.-]+\\.[A-Za-z]{2,6}$": {
"type": "object",
"properties": {
"__from": {
"type": "string"
},
"message": {
"type": "object",
"properties": {
"from": {
"type": "string"
},
"detail": {
"type": "string"
}
},
"required": [ "from","detail"]
}
},
"required": [ "__from","message"]
}
},
"additionalProperties": false
}