Related
I want to write a single file JSON schema definition with several sub schemas that I can combine, depending on the payload.
The following schema validates, that my schema is working with my sample JSON response. (The response object has a wrong type for payload.role to make sure the schema catches this mistake!)
For clarity, I reduce it on the most important parts. A full working example can be found here: https://www.jsonschemavalidator.net/s/3KAaXjtg
Schema
{
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "http://example.com/baseSchema.json",
"type": "object",
"required": [
"payload"
],
"properties": {
"payload": {
"$id": "#/properties/payload",
"type": "object",
// reference the right schema depending on the payload child key
// if `payload.user` reference `userSchema.json`
// if `payload.users` reference `usersSchema.json`
// if `payload.*` reference `*Schema.json`
"$ref": "userSchema.json"
}
},
"definitions": {
"user": {
"$id": "http://example.com/userSchema.json",
"type": "object",
"required": [
"user"
],
"properties": {
"user": {
"type": "object",
"$ref": "userProperties.json"
}
}
},
"users": {
"$id": "http://example.com/usersSchema.json",
"type": "object",
"required": [
"users"
],
"properties": {
"users": {
"type": "array",
"items": {
"$ref": "userProperties.json"
}
}
}
},
"userProperties": {
"$id": "http://example.com/userProperties.json",
"type": "object",
"properties": {
"firstName": {
"$id": "#/properties/payload/properties/user/properties/firstName",
"type": "string"
}
}
}
}
}
Response
{
"status": {
"code": 200,
"description": "User retrieved successfully."
},
"payload": {
"user": {
"firstName": "Joe",
"lastName": "Doe",
"role": "3", // for testing reasons, this is the wrong type!
"email": "doe#example.com",
"customerID": "",
"projects": [
"AIXG5mEg6QLl9rhVSE6m",
"Bs1bHiOIqKclwwis3CNf",
"NC2OUGVZXU35FA7iwRn4"
],
"status": "Status",
"id": "c555BSZnKLdHSRYqrU5hqiQo733j13"
}
}
}
So I've got a baseSchema.json that matches this response:
{
"status": {},
"payload": {}
}
payload gets extended by a certain key like payload.user = {} or payload.foo = {} and depending on that key, I want to extend schema with one of my definitions.
The following part only works for the key user:
"properties": {
"payload": {
"$id": "#/properties/payload",
"type": "object",
// reference the right schema depending on the payload child key
// if `payload.user` reference `userSchema.json`
// if `payload.users` reference `usersSchema.json`
// if `payload.*` reference `*Schema.json`
"$ref": "userSchema.json"
}
},
I failed to setup any conditions (with allOf, if, else), that would reference the correct sub-schema, based on the payload key.
Any hints and help to solve that is appreciated.
Schema and link to demo at the end... Let's look at how we got there...
In JSON Schema draft-07 and previous, you can't use $ref alongside other keywords. Other keywords are ignored. (In your schema http://example.com/userSchema.json you had type next to $ref). Fortunatly this didn't cause you any problems as you declare the type in the referenced schema. (You CAN do this with 2019-09 or above.)
The values for the keywords if, then, and else are schemas.
For the then subschema to be applied to your instance location, the if schema must come back as valid. If it fails, the else subschema value will be applied.
Our if condition checks for the presense of a specific key.
If the key exists, THEN apply the schema which references the correct schema.
Because you want the conditions to be mutually exclusive, you need to wrap the multiple conditions in a oneOf, and add else: false to the conditional checks. false as a schema makes validation fail.
Let me know if you want any further clarification on any of the above.
Demo: https://jsonschema.dev/s/HLniL
{
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "http://example.com/baseSchema.json",
"type": "object",
"required": [
"payload"
],
"properties": {
"payload": {
"$id": "#/properties/payload",
"type": "object",
"oneOf": [
{
"if": {
"required": [
"user"
]
},
"then": {
"$ref": "userSchema.json"
},
"else": false
},
{
"if": {
"required": [
"users"
]
},
"then": {
"$ref": "usersSchema.json"
},
"else": false
}
]
}
},
"definitions": {
"user": {
"$id": "http://example.com/userSchema.json",
"type": "object",
"required": [
"user"
],
"properties": {
"user": {
"$ref": "userProperties.json"
}
}
},
"users": {
"$id": "http://example.com/usersSchema.json",
"type": "object",
"required": [
"users"
],
"properties": {
"users": {
"type": "array",
"items": {
"$ref": "userProperties.json"
}
}
}
},
"userProperties": {
"$id": "http://example.com/userProperties.json",
"type": "object",
"properties": {
"firstName": {
"type": "string"
}
}
}
}
}
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"
]
}
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
}
}
}
Is "#/allOf/1/properties/body/properties/foo" a valid $ref?
As stated in this article which explains $ref usage, it looks like it should be.
However both AJV and https://www.jsonschemavalidator.net/ seem to disagree.
Trying with a plain JSON Pointer it definitely seems to be possible: https://repl.it/repls/WretchedSpiritedMammoth
This is the schema I'm testing with.
{
"$schema": "http://json-schema.org/draft-06/schema#",
"type": "object",
"allOf": [
{
"$schema": "http://json-schema.org/draft-06/schema#",
"$id": "http:/example.org/example.schema.json",
"type": "object",
"properties": {
"type": {
"type": "string"
},
"body": {
"type": "object"
}
},
"required": [
"type",
"body"
]
},
{
"properties": {
"body": {
"$schema": "http://json-schema.org/draft-06/schema#",
"$id": "http://example.org/foo.schema.json",
"type": "object",
"properties": {
"foo": {
"type": "number",
"minimum": 1
},
"bar": {
"$ref": "#/allOf/1/properties/body/properties/foo"
}
},
"required": [
"foo",
"bar"
]
}
}
}
]
}
EDIT: These are the errors im getting:
jsonschemavalidator.net
Error parsing schema
Message: Error when resolving schema reference '#/allOf/1/properties/body/properties/foo'.
Path 'allOf[1].properties.body.properties.bar', line .., position ..
AJV:
can't resolve reference #/allOf/1/properties/body/properties/foo from id http://example.org/foo.schema.json"
Your schema is mostly right, although you've set an $id in a subschema.
The "$id" keyword defines a URI for the schema, and the base URI that
other URI references within the schema are resolved against.
https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-00#section-9.2
By adding an $id to a subschema, you've reset the base URI for other URI references, which includes any use of $ref within the subschema and its children.
$ref
...Resolved against the current URI base, it identifies the URI of a
schema to use.
https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-00#section-8
By changing your definition of bar to the following, your schema will be valid.
"bar": {
"$ref": "#/properties/foo"
}
Further edit as Relequestual requested in his later comment:
It is valid to have an absolute URI as subschema $id, but as noted it resets the base URI. The restrictions on $id values only apply when using it to create local URI fragment identifiers (if this does not mean anything to you, do not worry about it).
Referencing a property schema from another property schema directly is valid, but the more common best practice is to put such a schema under the "definitions" keyword and have both properties refer to that location. This is what I would recommend for maximum clarity:
{
"$schema": "http://json-schema.org/draft-06/schema#",
"type": "object",
"allOf": [
{
"$schema": "http://json-schema.org/draft-06/schema#",
"$id": "http:/example.org/example.schema.json",
"type": "object",
"properties": {
"type": {
"type": "string"
},
"body": {
"type": "object"
}
},
"required": [
"type",
"body"
]
},
{
"properties": {
"body": {
"$schema": "http://json-schema.org/draft-06/schema#",
"$id": "http://example.org/foo.schema.json",
"type": "object",
"properties": {
"foo": {
"$ref": "#/definitions/whateverYouWantToCallIt"
},
"bar": {
"$ref": "#/definitions/whateverYouWantToCallIt"
}
},
"required": [
"foo",
"bar"
]
}
},
"definitions": {
"whateverYouWantToCallIt": {
"type": "number",
"minimum": 1
}
}
}
]
}
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"
}
}
}