Reputation: 82474
I have an object (from a 3rd party, so I can't change it) that have a property named "key", and another property called "value" that is optional, and it's type depends on the value of the "key" property.
For instance:
If the key is "comment", the type of value {"Text":"commentValue"}
.
If the key is "offset", the type of value is {"seconds":int}
.
If the key is "weather", the type of value is {"value": Enum["sun", "clouds", "rain"...]}
Moreover, some of the keys do not have the value property, so the schema should forbid it from appearing with these keys. one of these keys is "standby" (as you can see in my current attempt below)
I've tried manipulating the code samples from this SO answer, but couldn't make it work.
I'm currently attempting to validate output json against my schema attempts using Newtonsoft's JSON Schema Validator - but I can't seem to get the "value" property defined correctly.
This is my code so far:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "TestOptionalObject",
"type": "object",
"additionalProperties": false,
"required": [
"test"
],
"properties": {
"test": {
"$ref": "#/definitions/test"
}
},
"definitions": {
"test": {
"type": "object",
"additionalProperties": false,
"required": [
"key",
],
"properties": {
"key": {
"type": "string",
"enum": ["standby", "comment", "offset"]
},
"value" : {
"if": {
"properties": {
"key": {"enum": ["comment"]}
}
},
"then": {
"$ref": "#/definitions/commentValue"
},
"if": {
"properties": {
"key": {"enum": ["offset"]}
}
},
"then": {
"$ref": "#/definitions/offsetValue"
}
}
}
},
"commentValue" : {
"type": "object",
"additionalProperties": false,
"required": [
"text",
],
"properties": {
"text" : {"type" : "string"}
}
},
"offsetValue" : {
"type": "object",
"additionalProperties": false,
"required": [
"seconds",
],
"properties": {
"seconds" : {
"type": "integer",
"format": "int32"
}
}
}
}
}
And this is the error messages I get:
JSON does not match schema from 'then'. Schema path: #/definitions/offsetValue/then
Property 'text' has not been defined and the schema does not allow additional properties. Schema path: #/definitions/offsetValue/additionalProperties
Required properties are missing from object: seconds. Schema path: #/definitions/offsetValue/required
Json examples to validate:
Should fail:
{
"test": {
"key": "comment",
"value": {"seconds":12}
}
}
{
"test": {
"key": "standby",
"value": {"asdf":12}
}
}
Should pass:
{
"test": {
"key": "comment",
"value": {"text":"comment text"}
}
}
{
"test": {
"key": "offset",
"value": {"seconds":12}
}
}
Upvotes: 3
Views: 2946
Reputation: 323
If the key is "comment", the type of value {"Text":"commentValue"}.
If the key is "offset", the type of value is {"seconds":int}.
If the key is "weather", the type of value is {"value": Enum["sun", "clouds", "rain"...]}
This is a classic case of discriminated union, so can be expressed like this:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"anyOf": [
{
"type": "object",
"additionalProperties": false,
"properties": {
"key": {
"const": "comment"
},
"value": {
"type": "object",
"additionalProperties": false,
"properties": {
"Text": {
"type": "string"
}
}
}
}
},
{
"type": "object",
"additionalProperties": false,
"properties": {
"key": {
"const": "offset"
},
"value": {
"type": "object",
"additionalProperties": false,
"properties": {
"seconds": {
"type": "integer"
}
}
}
}
},
{
"type": "object",
"additionalProperties": false,
"properties": {
"key": {
"const": "weather"
},
"value": {
"type": "object",
"additionalProperties": false,
"properties": {
"value": {
"enum": ["sun", "clouds", "rain"]
}
}
}
}
},
{
"type": "object",
"additionalProperties": false,
"properties": {
"key": {
"enum": ["standby"]
}
}
}
]
}
It can probably be simplified further, but you get the idea.
Generally avoid using fancy JSON Schema features which don't map directly to basic type systems unless you absolutely have to. The whole point of JSON Schema is to represent serializable data in readable form both for machines and humans, not map the type system of the language running the server into JSON.
Upvotes: 1
Reputation: 12315
I have changed your JSON Schema so it does what you expect, apart form key of standby
as you didn't include that in your schema, and you should be able to replicate the pattern I've created to add new keys as required.
The major issue you had was a false assumption about where to place if/then/else
keywords. They are applicator keywords, and so must be applied to the object which you are checking the condition of, and not a properties
key value. Because you were using if/then/else
in the object which was a value of value
, you were applying if/then/else
to the value of value
rather than test
.
You needed your if
to apply to test
to get the correct scope for checking the key
property value.
Here is the resulting fixed schema:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "TestOptionalObject",
"type": "object",
"additionalProperties": false,
"required": [
"test"
],
"properties": {
"test": {
"$ref": "#/definitions/test"
}
},
"definitions": {
"test": {
"type": "object",
"required": [
"key"
],
"properties": {
"key": {
"type": "string",
"enum": [
"standby",
"comment",
"offset"
]
}
},
"allOf": [
{
"if": {
"properties": {
"key": {
"const": "comment"
}
}
},
"then": {
"properties": {
"value": {
"$ref": "#/definitions/commentValue"
}
}
}
},
{
"if": {
"properties": {
"key": {
"const": "offset"
}
}
},
"then": {
"properties": {
"value": {
"$ref": "#/definitions/offsetValue"
}
}
}
}
]
},
"commentValue": {
"type": "object",
"additionalProperties": false,
"required": [
"text"
],
"properties": {
"text": {
"type": "string"
}
}
},
"offsetValue": {
"type": "object",
"additionalProperties": false,
"required": [
"seconds"
],
"properties": {
"seconds": {
"type": "integer",
"format": "int32"
}
}
}
}
}
If you want any more help, please feel free to join the JSON Schema slack using the discussion link on the http://json-schema.org site.
Upvotes: 3