JSON Schema oneOf validation - json

I'm trying to create a JSON Schema that will allow a property to be either a number or an object of a specific format.
My data looks like this:
{
"num": 200
}
and my Schema looks like this:
{
"properties": {
"num": {
"type": [
"number",
"object"
],
"oneOf": [
{
"type": "number"
},
{
"$ref": "#/definitions/Variable"
}
]
}
},
"required": [
"num"
],
"additionalProperties": false,
"definitions": {
"Variable": {
"title": "Variable",
"properties": {
"$variable$": {
"type": "boolean",
"example": true
},
"name": {
"type": "string"
},
"defaultValue": {
"type": [
"string",
"object",
"number"
]
}
},
"required": [
"$variable$",
"name"
],
"additionalProperties": false
}
}
}
When I run it via a validator here: https://www.jsonschemavalidator.net/
I get this error:
Message: JSON is valid against more than one schema from 'oneOf'. Valid schema indexes: 0, 1.
Schema path: #/properties/num/oneOf
I'm assuming I'm missing something obvious about how oneOf works, but I can't figure out what it might me. Would appreciate any help here, thanks!

The error you are getting is telling you that both of you oneOf schemas are validating as true. It might be surprising that the value 4 is valid against the following schema.
{
"properties": {
"foo": { "type": "string": }
},
"required": ["foo"]
}
It turns out that the properties keyword and the required keyword don't apply when the value is not an object. So, the above schema is effectively the empty schema ({}) when validating against a number (or anything that is not an object). Because the empty schema means there are no constraints, everything is valid.
To fix your problem just add "type": "object" to your /definitions/Variable schema.

For your case you don't need oneOf at all, you can simply use
"type": ["number",{"$ref":"#/definitions/Variable"}] instead of "type": ["number","object"]
{
"properties": {
"num": {
"type": [
"number",{"$ref":"#/definitions/Variable"}
]
}
},
"required": [
"num"
],
"additionalProperties": false,
"definitions": {
"Variable": {
"title": "Variable",
"properties": {
"$variable$": {
"type": "boolean",
"example": true
},
"name": {
"type": "string"
},
"defaultValue": {
"type": [
"string",
"object",
"number"
]
}
},
"required": [
"$variable$",
"name"
],
"additionalProperties": false
}
}
}

Related

JSON Schema Conditional Statements

I am trying to validate what I thought was a simple JSON schema as a configuration file for my Python application, it's a list of header key/value pairs, the only complication is that if the 'Type' field is set to 'AnyValue' then the value key is not required.
Here is the schema as it is:
{
"definitions":
{
'KeyEntry':
{
"properties":
{
'Type': {"type" : "string"},
'Key': {"type": "string"}
},
"required": ['Type', 'Key'],
"anyOf":
[
{
"if":
{
"properties": {'Type': {"const": 'ExactValue'}}
},
"then":
{
"properties":
{
'Value': {"type": "string"}
},
"required": ['Type', 'Key', 'Value'],
"additionalProperties": false
}
},
{
"if":
{
"properties": {'Type': {"const": 'AnyValue'}}
},
"then":
{
"required": ['Type', 'Key'],
"additionalProperties": false
}
}
]
}
},
"type": "object",
"properties":
{
'Keys':
{
"type": "array",
"items": {"$ref": "#/definitions/KeyEntry"}
}
},
"required": ['Keys']
}
Most of the validation works, except if I add extra values, even though I have set "additionalProperties": false throughout the schema.
Here is an example where extra values are accepted:
{
"Keys": [
{
"Type": "AnyValue",
"Key": "Version",
"Y": "Yes",
"N": "No",
}
]
}
Please can someone help explain where I have gone wrong and how I should correct it, please?
additionalProperties draft-07...
Validation with "additionalProperties" applies only to the child values of instance names that do not match any names in "properties", and do not match any regular expression in "patternProperties".
This means that additionalProperties is only aware of keywords which appear in properties or that match the regex from patternProperties. Using additionalProperties with required without properties is going to create a dud schema (nothing will pass validation).
In stead, you can separate the concerns into what you actually want...
You want type, key, and value, to all be strings.
One of...
If type is AnyValue, then require type and key only.
If type is ExactValue, then require type, key, and value.
This is also easier to understand. Here's a live demo with your data.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"KeyEntry": {
"properties": {
"Type": {
"type": "string"
},
"Key": {
"type": "string"
},
"Value": {
"type": "string"
}
},
"additionalProperties": false,
"required": [
"Type",
"Key"
],
"anyOf": [
{
"if": {
"properties": {
"Type": {
"const": "ExactValue"
}
}
},
"then": {
"required": [
"Type",
"Key",
"Value"
]
},
"else": false
},
{
"if": {
"properties": {
"Type": {
"const": "AnyValue"
}
}
},
"then": {
"required": [
"Type",
"Key"
]
},
"else": false
}
]
}
},
"type": "object",
"properties": {
"Keys": {
"type": "array",
"items": {
"$ref": "#/definitions/KeyEntry"
}
}
},
"required": [
"Keys"
]
}

How to combine a property type, to match another properties type

I have the following use case with a JSON schema. I have a metadata object of a setting. In our case a setting can be of type string/real/integer/boolean.
In this object I have 4 fields: default/minimum/maximum each define a property of the setting.
Now what I want to achieve is that when the type of de default value is an integer, also the minimum/maximum values are integers.
The schema I have come up with so far:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"setting-value": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
}
]
},
"setting-meta": {
"type": "object",
"required": [
"name",
"type",
"default"
],
"properties": {
"name": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"Real",
"Integer",
"Boolean",
"String"
]
},
"minimum": {
"$ref": "#/definitions/setting-value"
},
"maximum": {
"$ref": "#/definitions/setting-value"
},
"default": {
"$ref": "#/definitions/setting-value"
},
"value": {
"$ref": "#/definitions/setting-value"
}
}
}
}
}
Here it is possible for the #/definitions/setting-meta to have support for the different types. However it does not define that if for example the value of TYPE is equal to Real/Integer that the types of minimum/maximum/default/value should all be of type number.
I would use these definitions as follows
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$ref": "schema-definition-above.json#/definitions/setting-meta"
}
According the the current schema, all examples below are VALID, however they should be valid/invalid as suggested:
Valid JSon object:
{
"name": "Enabled",
"type": "Boolean",
"minimum": false,
"maximum": true,
"default": true,
"value": true
}
Invalid json object, minimum/maximum/default don't have the same type:
{
"name": "Enabled",
"type": "Boolean",
"minimum": false,
"maximum": 1,
"default": "value",
"value": true
}
Invalid json object: type, does not match the actual type of the values
{
"name": "Enabled",
"unit": "enabled/disabled",
"configId": "Accumulator",
"displayName": "Enable or disable this machine",
"type": "Integer",
"minimum": false,
"maximum": true,
"default": true,
"value": true
}
My question:
Is it possible to put these kinds of dependencies into a JSON schema? The only kind of dependency I have foudn so far is with property dependencies indicating that if one property is set, another should also be set.
Any help would be much appreciated.
EDIT:
Extended that use case with some JSON objects that should be validated or invalidated with the referenced schema.
In order to do conditional validation where you have a known set of possible conditions, you should use the if/then/else keywords, in combination with with allOf.
In this schema, the first schema in allOf defines your general structure and overall requirements. The second schema applies the then constraint if the if schema validates successfully.
You would need to replicate the second schema for each condition that you have.
You can see this schema working at https://jsonschema.dev (link is preloaded with the below schema and sample data)
(The use of patternProperties is just a space saver. You could define each property individually.)
{
"$schema": "http://json-schema.org/draft-07/schema#",
"allOf": [
{
"properties": {
"type": {
"enum": [
"Real",
"Integer",
"Boolean",
"String"
]
}
},
"required": [
"type",
"default",
"name"
]
},
{
"if": {
"properties": {
"type": {
"const": "String"
}
}
},
"then": {
"patternProperties": {
"^(minimum|maximum|default|value)$": {
"type": [
"string"
]
}
}
}
}
]
}

JSON-Schema oneOf for option under root area

I am trying to get the "oneof" to allow for options in root items but can't find an example and what I try gives an error.
I can get it to work if it is under another item but not under the root {'s
Example - a Job Payment that has required fields (jobNum, payee, amount, type, ) and an option for the payment type (checkInfo or dollarAmt). I know this could be done other ways, but I need this method for a more complex schema.
{
"jobNum": "x216",
"payee": "John Doe",
"type": "check",
"amount": "112.25",
"checkInfo": {
"number": "386"
}
}
{
"JobNum": "x216",
"Payee": "John Doe",
"type" : "Cash",
"amount" : "112.25",
"cashInfo" : {
"dollarAmt" : "112",
"coinAmt" : "0.25"
}
}
The following gives me this error - "Unexpected token encountered when reading value for 'oneOf'. Expected StartObject, Boolean, got StartArray"
{
"description": "Job Payment",
"type": "object",
"required": [ "jobNum", "payee", "amount", "type"],
"properties": {
"jobNum": {
"type": "string"
},
"payee": {
"type": "string"
},
"amount": {
"type": "string"
},
"type": {"enum": [ "check", "cash" ]
},
"oneOf": [
{ "$ref": "#/definitions/ptCash" },
{ "$ref": "#/definitions/ptCheck" }
]
},
"definitions": {
"ptCash": {
"properties": {
"checkInfo": {
"number": "string"
}
},
"required": [ "checkInfo" ],
"additionalProperties": false
},
"ptCheck": {
"properties": {
"dollarAmt": {
"type": "string"
},
"coinAmt": {
"type": "string"
}
},
"required": [ "dollarAmt", "coinAmt" ],
"additionalProperties": false
}
},
"additionalProperties": false
}
There are a a few issues with your schema. I fixed it for you below. I won't explain all the changes I made because I think it is mostly pretty clear by reading the schema. If you want more detail on anything, just ask and I'll update the answer with more details.
The oneOf keyword can only appear in a schema. The properties keyword is an object whose values are schemas. When you put "oneOf" directly under properties, it's not interpreted as a keyword, it's interpreted as a property called "oneOf". The validator then complains because the value of property "oneOf" is expected to be a schema, not an array of schemas like the oneOf keyword.
Your use of additionalProperties doesn't work. This keyword doesn't work the way people often assume that it does. JSON Schema keywords are not aware of any state outside of the schema they are in. Let's look at the "ptCheck" branch of your oneOf first. This describes the property "number", says it is required and that there may be no keywords other than "number". Then your top level defines a the properties "jobNum", "payee", "amount", and "type", requires them all and allows no other properties. These two things can never be true at the same time. Even though your schema is valid, there is no JSON value that can ever be valid against this schema. That's why I moved the definitions of "checkInfo" and "cashInfo" to the top level and only put the required part in oneOf. The only downside to this approach is that you can pass both a "checkInfo" and a "cachInfo" object and it will validate. The extraneous property gets ignored. There are ways around this, but they are problematic enough that I don't advise using them.
I always advise people not to use "additionalProperties": false and to ignore unknown properties instead. The reason is that JSON Schema is a constraint system. Any valid JSON is valid against the empty schema ({}) and each keyword in the schema adds some constraint. This is a different approach to what people are used to when defining classes. An empty class describes nothing and valid values are added. We use "additionalProperties": false to get JSON Schema to behave more like defining a class, but trying to get JSON Schema to behave like something it isn't causes challenges like the one you see here.
{
"description": "Job Payment",
"type": "object",
"required": ["jobNum", "payee", "amount", "type"],
"properties": {
"jobNum": { "type": "string" },
"payee": { "type": "string" },
"amount": { "type": "string" },
"type": { "enum": ["check", "cash"] },
"checkInfo": {
"type": "object",
"properties": {
"number": { "type": "string" }
},
"required": ["number"]
},
"cashInfo": {
"type": "object",
"properties": {
"dollarAmt": { "type": "string" },
"coinAmt": { "type": "string" }
},
"required": ["dollarAmt", "coinAmt"]
}
},
"oneOf": [
{ "$ref": "#/definitions/ptCash" },
{ "$ref": "#/definitions/ptCheck" }
],
"definitions": {
"ptCheck": {
"type": "object",
"properties": {
"type": { "enum": ["check"] }
},
"required": ["checkInfo"]
},
"ptCash": {
"type": "object",
"properties": {
"type": { "enum": ["cash"] }
},
"required": ["cashInfo"]
}
},
"additionalProperties": false
}
oneOf should be placed in prope
Have to re-write rule for both ptCash and ptCheck by using type: object
Following schema should work with ptCheck:
{
"description": "Job Payment",
"type": "object",
"required": [ "jobNum", "payee", "amount", "type"],
"properties": {
"jobNum": {
"type": "string"
},
"payee": {
"type": "string"
},
"amount": {
"type": "string"
},
"type": {"enum": [ "check", "cash" ]
}
},
"oneOf": [
{ "$ref": "#/definitions/ptCash" },
{ "$ref": "#/definitions/ptCheck" }
],
"definitions": {
"ptCash": {
"properties": {
"checkInfo": {
"type": "object",
"required": ["number"],
"properties": {
"number": {
"type": "string"
}
}
}
},
"required": [ "checkInfo" ]
},
"ptCheck": {
"properties": {
"cashInfo": {
"type": "object",
"properties": {
"dollarAmt": {
"type": "string"
},
"coinAmt": {
"type": "string"
}
},
"required": ["dollarAmt", "coinAmt"]
}
},
"required": ["cashInfo"]
}
}
}
Provide some example as below:
import jsonschema
import simplejson as json
schema_filename = '47926398.json'
with open(schema_filename, 'r') as f:
schema_data = f.read()
schema = json.loads(schema_data)
# validate with checkInfo
json_obj = {
"jobNum": "x216",
"payee": "John Doe",
"type": "check",
"amount": "112.25",
"checkInfo": {
"number": "386"
}
}
jsonschema.validate(json_obj, schema)
# invalidate
json_obj = {
"jobNum": "x216",
"payee": "John Doe",
"type": "check",
"amount": "112.25",
"checkInfox": {
"number": "386"
}
}
jsonschema.validate(json_obj, schema)
# validate with cashInfo
json_obj = {
"jobNum": "x216",
"payee": "John Doe",
"type": "check",
"amount": "112.25",
"cashInfo": {
"dollarAmt": "400",
"coinAmt": "30"
}
}
jsonschema.validate(json_obj, schema)
# invalidate with cashInfo
json_obj = {
"jobNum": "x216",
"payee": "John Doe",
"type": "check",
"amount": "112.25",
"cashInfox": {
"dollarAmt": "400",
"coinAmt": "30"
}
}
jsonschema.validate(json_obj, schema)
# invalidate with cashInfo.dollarAmtx
json_obj = {
"jobNum": "x216",
"payee": "John Doe",
"type": "check",
"amount": "112.25",
"cashInfo": {
"dollarAmtx": "400",
"coinAmt": "30"
}
}
jsonschema.validate(json_obj, schema)

JSON validation against JSON schemas: Why is this obvious JSON data not failing validation

This JSON file should fail validation but it does not. Someone tell me why.
Plug the below json data and schema into this web site, validate,
http://json-schema-validator.herokuapp.com
and I get the same results in the Mule Validate JSON Schema. Its obviously does not comply with the schema (i added some fields, I misspelled some fields, the date-time value is not a real date time) but yet it does not fail it. Can someone tell me why?
JSON Schema:
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "http://hud.gov/ocio/xsd/esb/serviceauditingframework/2.0#",
"definitions": {
"serviceAuditLogData": {
"type": "object",
"title": "serviceAuditLogData",
"required": [
"serviceRequestTimestamp",
"sourceSystem"
],
"properties": {
"auditId": {
"type": "string"
},
"serviceRequestTimestamp": {
"type": "string",
"format": "date-time"
},
"serviceProvider": {
"type": "string"
},
"serviceProviderVersion": {
"type": "string"
},
"serviceProviderTimestamp": {
"type": "string",
"format": "date-time"
},
"eventType": {
"type": "string"
},
"eventDetail": {
"type": "string"
},
"hostName": {
"type": "string"
},
"sourceSystem": {
"type": "string"
},
"authenticationId": {
"type": "string"
},
"endUserId": {
"type": "string"
},
"inputData": {
"type": "string"
}
},
"propertiesOrder": [
"auditId",
"serviceRequestTimestamp",
"serviceProvider",
"serviceProviderVersion",
"serviceProviderTimestamp",
"eventType",
"eventDetail",
"hostName",
"sourceSystem",
"authenticationId",
"endUserId",
"inputData"
]
}
}
}
JSON Data
{
"serviceAuditLogData": {
"junk":"asdfasdf",
"serviceRequestTimestamp": "2004-09-29T12:58:31.470Z",
"serviceProvider": "FLQS",
"serviceProviderVersion": "v1.0.1",
"audit_id": "17f24136-2494-4bf8-9d3b-9baafaae0cc9",
"serviceProviderTimestamp": "2012-11-04T21:44:57.997Z",
"eventType": "Query Pool",
"eventDetail": "default pool",
"hostName": "esb-d-srv1.",
"sourceSystem": "LRS",
"authenticationId": "EsbLrsAccount",
"endUserId": "H574857",
"inputData": "L234234234, L32453462345, L23452346"
}
}
It does not fail because your schema does not enforce any constraint. Notice that definitions is not a jsonschema keyword that constraints validation. It is normally used to place sub-schemas that are re-used in other parts of the schema definition. Thus, to start with, you should change the definitions keyword for properties.
Another common misunderstanding with jsonschema is related to the properties keyword. Let's take the following example:
{
"type" : "object",
"properties" : {
"key1" : {
"type" : "string"
}
}
}
You must read it as: json must be an object, and in the case that it contains a key equal to key1, its value must be a string. According to that the following two json objects are valid:
{
"key2":12
}
And:
{
"key1":"sdf"
}
Finally, related to date-time format, you must check the section 6 of RFC3339 to be sure you have a valid date-time. And in any case, the implementation of formats is not compulsory in jsonschema validators.
Thanks #jruizaranguren I also learned that I needed to place
"additionalProperties": false, and "required": to make sure that whats' being passed in the API is what's expected.
The below is how I solved my problem.
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"definitions": {
"serviceAuditLogData": {
"type": "object",
"additionalProperties": false,
"required": [
"auditCorrelationId",
"serviceRequestTimestamp",
"serviceProvider",
"serviceProviderVersion",
"serviceProviderTimestamp",
"eventType",
"hostName",
"sourceSystem",
"authenticationId"
],
"properties": {
"auditCorrelationId": {
"type": "string"
},
"serviceRequestTimestamp": {
"type": "string",
"format": "date-time"
},
"serviceProvider": {
"type": "string"
},
"serviceProviderVersion": {
"type": "string"
},
"serviceProviderTimestamp": {
"type": "string",
"format": "date-time"
},
"eventType": {
"type": "string"
},
"eventDetail": {
"type": "string"
},
"hostName": {
"type": "string"
},
"sourceSystem": {
"type": "string"
},
"authenticationId": {
"type": "string"
},
"endUserId": {
"type": "string"
},
"inputData": {
"type": "string"
}
}
}
},
"additionalProperties": false,
"required": [
"serviceAuditLogData"
],
"properties": {
"serviceAuditLogData": {
"$ref": "#/definitions/serviceAuditLogData"
}
}
}

A common JSON Schema for similar structure

I'm completely new to json and json schema, so I have a question (yet I don't know how much it make sense). Can we create a json schema which is common for similar type of structure. For example:
One single schema can be used to validate following json
JSON:
{
"Team_Table":
[{"Name":"New Zealand", "Match":"Six", "Won":"Six"}]
}
And
{
"Story_Taller":
[{"Story":"No Name", "Chapter":"Don't know"}]
}
Similarities:
Both have only one object in the array
Objects have string value.
Dissimilarities:
Number of properties are different
Keys are different in both
Can we do this?
Maybe this helps you along:
{
"properties": {
"Story_Taller": {
"type": "array",
"maxItems": 1,
"items": {
"properties": {
"Chapter": {
"type": "string"
},
"Story": {
"type": "string"
}
},
"additionalProperties": false
}
},
"Team_Table": {
"type": "array",
"maxItems": 1,
"items": {
"properties": {
"Name": {
"type": "string"
},
"Match": {
"type": "string"
},
"Won": {
"type": "string"
}
},
"additionalProperties": false
}
}
},
"oneOf": [
{
"title": "Story_Taller",
"required": [
"Story_Taller"
]
},
{
"title": "Team_Table",
"required": [
"Team_Table"
]
}
]
}
in (short) words:
in your JSON there must be one property of either "Story_Taller" or "Team_Table" with a maximum of 1 item
"oneOf": [ ... ]
Properties of both arrays are defined by items
"Story_Taller" must have "Chapter" and "Story" and no additional properties.
"Team_Table" must have "Name", "Match", "Won" and no additional properties.
And all of them are defined as strings.