JSON schema "oneOf" for validating existence of keys - json

I'm trying to create a JSON schema that validates against multiple different entities that have mostly the same attributes but only differ in a few attributes.
{
"firstname": "Bat",
"lastname": "man",
"email": "batman#gmail.com"
}
{
"firstname": "Super",
"lastname": "man",
"phone": "543-453-4523"
}
{
"firstname": "Wonderwo",
"lastname": "man",
"email": "wonderwoman#gmail.com"
}
Basically I want to create a single schema that makes sure the last name is "man" and either have a phone or email attribute.
I was trying to implement this using oneOf, like this:
{
"properties": {
"firstname": {
"type": "string"
},
"lastname": {
"type": "string",
"pattern": "man"
},
"oneOf": [{
"email": {
"type": "string"
},
"phone": {
"type": "string"
}
}]
}
}
But I don't think this works. Is something like this even possible with JSON schema? And how can I achieve this?

You have several problems:
"oneOf" is a keyword, in cannot be used inside properties.
items inside "oneOf" should be schemas, what you have there is not.
"anyOf" is almost always better than "oneOf", unless you really need an exclusive "OR"
"pattern" is a wrong keyword here, you need "enum" (or draft-06 "const")
You need:
{
"type": "object",
"required": ["firstname", "lastname"],
"properties": {
"firstname": {
"type": "string"
},
"lastname": {
"type": "string",
"enum": ["man"]
},
"email": {
"type": "string"
},
"phone": {
"type": "string"
}
},
"anyOf": [
{ "required": ["email"] },
{ "required": ["phone"] }
]
}

Related

JsonSchema definition path and subschema re-use

Lets say I have two schemas defined as follows -
ADDRESS_CLASS_SCHEMA_DEFINITION = {
"title": "Address",
"type": "object",
"properties": {
"country_code": {
"$ref": "#/definitions/CountryCode"
},
"city_code": {
"title": "City Code",
"type": "string"
},
"zipcode": {
"title": "Zipcode",
"type": "string"
},
"address_str": {
"title": "Address Str",
"type": "string"
}
},
"required": [
"country_code",
"city_code",
"zipcode"
],
"definitions": {
"CountryCode": {
"title": "CountryCode",
"description": "An enumeration.",
"enum": [
"CA",
"USA",
"UK"
],
"type": "string"
}
}
}
EMPLOYEE_CLASS_SCHEMA_DEFINITION = {
"title": "Employee",
"type": "object",
"properties": {
"id": {
"title": "Id",
"type": "integer"
},
"name": {
"title": "Name",
"type": "string"
},
"email": {
"title": "Email",
"type": "string"
},
"telephone": {
"title": "Telephone",
"type": "string"
},
"address": {
"$ref": "#/definitions/Address"
}
},
"required": [
"id",
"name",
"email"
],
"definitions": {
"Address": ADDRESS_CLASS_SCHEMA_DEFINITION
}
}
I'm trying to re-use sub-schema definitions by defining a constant and referencing them individually in definitions (for example address-schema is referenced through constant in employee-schema definition). This approach works for individual schemas, however there seems to be a json-pointer path issue for Employee schema - #/definitions/CountryCode wouldn't resolve in Employee schema. I was assuming that #/definitions/CountryCode would be a relative path on Address schema as its scope is defined on a sub-schema, but my understanding seems wrong. I can make it work by flattening out like below, however I donot want to take this route -
{
"title": "Employee",
"type": "object",
"properties": {
"id": {
"title": "Id",
"type": "integer"
},
"name": {
"title": "Name",
"type": "string"
},
"email": {
"title": "Email",
"type": "string"
},
"telephone": {
"title": "Telephone",
"type": "string"
},
"address": {
"$ref": "#/definitions/Address"
}
},
"required": [
"id",
"name",
"email"
],
"definitions": {
"CountryCode": {
"title": "CountryCode",
"description": "An enumeration.",
"enum": [
"CA",
"USA",
"UK"
],
"type": "string"
},
"Address": {
"title": "Address",
"type": "object",
"properties": {
"country_code": {
"$ref": "#/definitions/CountryCode"
},
"city_code": {
"title": "City Code",
"type": "string"
},
"zipcode": {
"title": "Zipcode",
"type": "string"
},
"address_str": {
"title": "Address Str",
"type": "string"
}
},
"required": [
"country_code",
"city_code",
"zipcode"
]
}
}
}
I'm wondering how to fix this, I've briefly looked into jsonschema-bundling and using $id but from best practices it seems like the general recommendation is to use $id when dealing with URI's alone. Would like to know about best practices and how to fix this problem, would also appreciate if someone can point me on how to use $id correctly (for example, constant based approach seems to work when I provide identifiers like $id: Address, $id: Employee). Thanks in advance.
JSON Schema implementations work in JSON land. When you combine your schemas in your example above, presumably in javascript/node.js, by the time it gets to the JSON Schema implementation for validation execution, any knowledge that there were separate schemas is lost. (It's generally not considered that this approach is the best approach.)
The EASY fix here SHOULD be just to define $id in each of the roots of your schemas. These should be a fully qualfied URI. It doesn't really matter what they are at this point. They could be https://example.com/a and https://example.com/b. Then, in the primary schema, you can do $ref: https://example.com/b.
Implementations should provide you with a way to load in your other/non-primary schemas so the $id values can be stored in an index. Using $id in your other schema with a fully qualified URI will signify a "resource boundary".
https://json-schema.hyperjump.io is the only web playground to support multiple files/schemas/"Schema Resources", so you can test this out there to confirm your expectations.
Not all implementations make it easy or even provide a means to import your other schemas, but they should.
If you have follow up questions, feel free to leave a comment, or join the JSON Schema slack server if it would be off-topic for StackOverflow.

JSON Schema if-else condition complex scenario

{
"policyHolder": {
"fullName": "A"
},
"traveller": [
{
"fullName": "B",
"relationship": "Spouse"
},
{
"fullName": "A",
"relationship": "My Self"
}
]
}
In above json, I want to validate that
if "relationship" = "My Self" then fullName must match the fullName in policyHolder
A field relationship must exist in traveller array, else json is invalid
I have tried to create a json schema with if-else, allOf, etc. but nothing works which can do these validations but not able to.
Please help!!
Schema:
{
"type": "object",
"required": [
"policyHolder",
"traveller",
],
"properties": {
"policyHolder": {
"$id": "#/properties/policyHolder",
"type": "object",
"required": [
"fullName"
],
"properties": {
"fullName": {
"$id": "#/properties/policyHolder/properties/fullName",
"type": "string",
}
}
},
"traveller": {
"$id": "#/properties/traveller",
"type": "array",
"minItems": 1,
"items": {
"$id": "#/properties/traveller/items",
"type": "object",
"properties": {
"fullName": {
"$ref": "#/properties/policyHolder/properties/fullName"
},
"relationship": {
"$id": "#/properties/traveller/items/properties/relationship",
"type": "string",
}
},
"required": [
"fullName",
"relationship"
],
}
}
}
}```
It's your first requirement that you're going to have the most trouble with. JSON Schema doesn't support validation of data against data elsewhere in the instance. It's a highly discussed topic, but nothing has been adopted yet. I suggest you verify this with a little code.
For the second, I would suggest you extract some of your subschemas into definitions rather than trying to muck about with IDs. IDs are typically more beneficial if you're referencing them from other documents or if you use short (like single-word) IDs. Defining the ID as its location in the document is redundant; most processors will handle this automatically.
{
"type": "object",
"required": [
"policyHolder",
"traveller",
],
"definitions": {
"person": {
"type": "object"
"properties": {
"fullName": {"type": "string"}
},
"required": ["fullName"]
},
"relationship": { "enum": [ ... ] } // list possible relationships
},
"properties": {
"policyHolder": { "$ref": "#/definitions/person" },
"traveller": {
"type": "array",
"minItems": 1,
"items": {
"allOf": [
{ "$ref": "#/definitions/person" },
{
"properties": {
"relationship": { "$ref": "#/definitions/relationship" }
},
"required": ["relationship"]
}
]
}
}
}
}
(I extracted the relationship into its own enum definition, but this is really optional. You can leave it inline, or even an unrestricted string if you don't have a defined set of relationships.)
This can't currently be done with JSON Schema. All JSON Schema keywords can only operate on one value at a time. There's a proposal for adding a $data keyword that would enable doing this kind of validation, but I don't think it's likely to be adopted. $data would work like $ref except it references the JSON being validated rather than referencing the schema.
Here's what how you would solve your problem with $data.
{
"type": "object",
"properties": {
"policyHolder": {
"type": "object",
"properties": {
"fullName": { "type": "string" }
}
},
"traveler": {
"type": "array",
"items": {
"type": "object",
"properties": {
"fullName": { "type": "string" },
"relationship": { "type": "string" }
},
"if": {
"properties": {
"relationship": { "const": "My Self" }
}
},
"then": {
"properties": {
"fullName": { "const": { "$data": "#/policyHolder/fullName" } }
}
}
}
}
}
}
Without $data, you will have to do this validation in code or change your data structure so that it isn't necessary.

Json schema ref other file

With this schema:
{
"$schema": "http://json-schema.org/draft-04/schema#",
"b": {
"type": "object",
"properties": {
"c": {
"type": "string"
}
}
},
"type": "object",
"properties": {
"a": {
"$ref": "#/b"
}
}
}
I can validate this example:
{
"a": {
"c": "test"
}
}
Now I want to create a new schema file for the "b" element and refer it in my 1st schema. How can I do this ? I try a lot of things but I always obtain jsonspec.reference.exceptions.NotFound: u'b.json' not registered.
After a couple of hours of googling and various doc readings, I've finally managed to be able to compose two json-schema's using "$ref" : "file:...".
My example data looks like this:
john-doe.json:
{
"first_name": "john",
"last_name": "doe",
"age": 42,
"address": {
"street_address": "foo street 42",
"city": "baklavastan",
"state": "foobar"
"foo": 42
}
}
And then I have two json schema files, which lives in the same directory on my file system.
customer.json
{
"type": "object",
"properties": {
"first_name": { "type": "string" },
"last_name": { "type": "string" },
"age" : { "type" : "integer" },
"address" : { "$ref" : "file:address.json#" }
},
"required": ["first_name", "last_name", "age", "address"],
"additionalProperties": false
}
address.json
{
"type": "object",
"properties": {
"street_address": { "type": "string" },
"city": { "type": "string" },
"state": { "type": "string" }
},
"required": ["street_address", "city", "state"],
"additionalProperties": false
}
I am able to validate against the schema in Clojure using the library scjsv (which is a wrapper around the Java json-schema-validation library)
It gives me following validation error (in clojure edn) for the john-doe.json example:
{ :level "error",
:schema {:loadingURI "file:address.json#", :pointer ""},
:instance {:pointer "/address"},
:domain "validation",
:keyword "additionalProperties",
:message
"object instance has properties which are not allowed by the schema: [\"foo\"]",
:unwanted ["foo"] }

Can maxItems/minItems be used with a $ref in a JSON schema

Given a JSON schema with the following in the definitions section:
"phoneNumber": {
"type": "object",
"properties": {
"countryCode": {
"type": "number"
},
"areaCode": {
"type": "number"
},
"number": {
"type": "number"
},
"extension": {
"type": "number"
},
"service": {
"type": "string",
"enum": ["Voice", "Fax", "Data"]
},
"class": {
"type": "string",
"enum": ["Switchboard", "Direct", "PA", "Mobile"]
}
}
}
If I want to include phoneNumber elsewhere using a $ref and want the JSON to validate if it contains multiple occurrences of phoneNumber, can I use maxItems/minItems:
"person": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"phoneNumber": {
"$ref": "#/definitions/phoneNumber"
//can I use maxItems/minItems here?
}
}
}
Can I use maxItems and minItems here, or would I have to do something like this below for it to validate:
"phoneNumber": {
"allOf": { "$ref": "#/definitions/phoneNumber" },
"maxItems": 4
}
$ref must stand alone. The option you identified using allOf is the best way to do it.
Any members other than "$ref" in a JSON Reference object SHALL be ignored.
https://datatracker.ietf.org/doc/html/draft-pbryan-zyp-json-ref-03#section-3

"required" keyword in JSON schema

I got the below schema from http://json-schema.org/examples.html, i want to know if the required keyword can only come at the top level. or it can also come within the properties if there is a property of type object.I could not find any thing related to this in the specification https://datatracker.ietf.org/doc/html/draft-fge-json-schema-validation-00#section-5.4.3.
{
"title": "Example Schema",
"type": "object",
"properties": {
"firstName": {
"type": "string"
},
"lastName": {
"type": "string"
},
"age": {
"description": "Age in years",
"type": "integer",
"minimum": 0
}
},
"required": ["firstName", "lastName"]
}
So the below example is a valid schema
{
"title":"Example Schema",
"type":"object",
"properties":{
"firstName":{
"type":"string"
},
"lastName":{
"type":"string"
},
"age":{
"type":"object",
"properties":{
"minAge":{
"type":"number"
},
"maxAge":{
"type":"number"
},
"required":[
"minAge",
"maxAge"
]
}
}
},
"required":[
"firstName",
"lastName"
]
}
4.4 Keywords with the possibility to validate container instances (arrays
or objects) only validate the instances themselves and not their
children (array items or object properties).
So I see that yes, you can have those on any level but the validation should be only consider on the same level as required
Yes, required is a valid keyword in any schema. There are no restrictions for nested schemas.
To use your example, the following is a valid schema and will validate the way you want it to.
{
"title": "Example Schema",
"type": "object",
"properties": {
"firstName": {
"type": "string"
},
"lastName": {
"type": "string"
},
"age": {
"type": "object",
"properties": {
"minAge": {
"type": "number"
},
"maxAge": {
"type": "number"
}
},
"required": [
"minAge",
"maxAge"
]
}
},
"required": [
"firstName",
"lastName"
]
}
The required keyword can be present in any schema. This is true of all schema keywords.
(There is a special-case for the meta-keyword $schema, for which it is advisable to only have in the top level)