How do I represent polymorphic objects in JSON schema? - json

I'm trying to create a schema for a JSON object that varies its schema depending on the value of one of its properties: type.
Like this:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["INT", "PERCENT"]
}
},
"required": ["type"],
"allOf": [
{
"if": {
"properties": {"type": {"const": "INT"}}
},
"then": {
"properties": {
"value": {"type": "number", "multipleOf": 1}
},
"required": ["value"],
"additionalProperties": false
}
},
{
"if": {
"properties": {"type": {"const": "PERCENT"}}
},
"then": {
"properties": {
"value": {"type": "number"},
"min": {"type": "number"},
"max": {"type": "number"}
},
"required": ["value", "min", "max"],
"additionalProperties": false
}
}
]
}
But I'm getting all sorts of misbehaviour from the various validators I'm trying.
Some examples with problems noted after the //:
{
"type": "PERCENT", // property type has not been defined(?!)
"value": 0.0,
"min": 10,
"max": 25
}
{
"type": "INT", // no errors allowed, and value
"value": 0.1, // should've been flagged as not multiple of 1
"min": 10, // should've been flagged as disallowed additional property
"max": 25 // same as above
}
Here's the validator I'm trying
Thanks in advance for your help!

It seems that you've figured out that the problem is that addtionalProperties only considers the properties defined in the sub-schema it's defined in. That's why you have to include the "type" property in your then. You have a few options that are an improvement over the solution you posted that you weren't happy with.
Option 1: unevaluatedProperties
Draft 2019-09 introduced the unevaluatedProperties keyword. If you use this at the top level of your schema and don't use addtionalProperties anywhere, you will get the behavior you expected from additionalProperties. I see you are using draft-07, so you have to upgrade in order to use this solution.
Option 2: Boolean schemas
The solution you came up with is to re-define the "type" property in the then schemas. additionalProperties just needs "type" to be declared, you don't need to include the schema for it again. You can use true or the empty schema {} instead.
Option 3: propertyNames
Instead of additionalProperties you can use propertyNames. This allows you to declare a list of which property names are allowed in the object rather than hacking additionalProperties with true or {} to figure out the allowed property names the way you want it to.

well I found a way to get what I wanted, but it doesn't mean I like it. Having to redefine "type" doesn't seem right to me.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["INT", "PERCENT"]
}
},
"required": ["type"],
"allOf": [
{
"if": {
"properties": {"type": {"const": "INT"}}
},
"then": {
"properties": {
"value": {"type": "number", "multipleOf": 1},
"type": {"type": "string"}
},
"required": ["value", "type"],
"additionalProperties": false
}
},
{
"if": {
"properties": {"type": {"const": "PERCENT"}}
},
"then": {
"properties": {
"value": {"type": "number"},
"min": {"type": "number"},
"max": {"type": "number"},
"type": {"type": "string"}
},
"required": ["value", "min", "max", "type"],
"additionalProperties": false
}
}
]
}
tests...
This validates:
{
"type": "PERCENT",
"value": 0,
"min": 10,
"max": 20
}
so does this:
{
"type": "INT",
"value": 0
}
this errors in the way I want:
{
"type": "INT",
"value": 0.1, // not a multiple of 1
"min": 4, // not expected
"max": 5 // not expected
}
and so does this:
{
"type": "PERCENT",
"value": 0.1,
"max": 5 // min is a required property
}

Related

Why can't the schema see the required array?

I have created a JSON Schema, but when I try to use it to validate another object, the schema fails due to an error, citing:
Message:
Required properties are missing from object: Envelope.
Schema path:
#/required
However, I can clearly see the required array listed inside of the envelope as a sibling to its properties. In every schema I've seen online, this is how is is displayed. I have included a trimmed-down selection of the schema below that still sees the same error. Can anyone tell me why this is failing?
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"Envelope": {
"type": "object",
"properties": {
"TP_UNIQUE_NUM": {
"length": 50,
"type": "string"
},
"SENDER_ID": {
"length": 50,
"type": "string"
},
"TRANSMISSION_FORMAT": {
"length": 50,
"type": "string"
},
"RECEIVER_ID": {
"length": 50,
"type": "string"
},
"HEADER": {
"type": "object",
"properties": {
"door": {
"description": "Door at which product associated with ASN is scheduled to be unloaded",
"type": "string",
"mandatory": "Y",
"maxLength": 10
},
"notes": {
"description": "Comments associated with ASN",
"type": "string",
"mandatory": "Y",
"maxLength": 4000
},
"wareHouseReference": {
"description": "Additional notes or reference information for the warehouse",
"type": "string",
"mandatory": "Y",
"maxLength": 100
}
},
"required": [
"wareHouseReference"
]
},
"TP_TRANSMISSION_TYPE": {
"maxLength": 50,
"type": "string"
},
"FILE_NAME": {
"maxLength": 50,
"type": "string"
},
"TRANSMISSION_DATE_TIME": {
"maxLength": 50,
"type": "string"
},
"TRANSACTION_COUNT": {
"maxLength": 50,
"type": "string"
}
}
}
},
"required": [
"Envelope"
]
}
It seems that you are missing a required array within the Envelope object.
You have a required array at the root level object, you also have one within the HEADER object, but not within the Envelope object.
I'm assuming a required array is required for each object type.
EDIT: My assumption is probably wrong, I was able to run your schema at json-schema-validator without an issue using this sample JSON. #Jason Desrosiers comment seems to be right on the money.
{
"Envelope": {
"TP_UNIQUE_NUM": "hello",
"SENDER_ID": "hello",
"TRANSMISSION_FORMAT": "hello",
"RECEIVER_ID": "hello",
"HEADER": {
"door": "hello",
"notes": "hello",
"wareHouseReference": "hello"
},
"TP_TRANSMISSION_TYPE": "hello",
"FILE_NAME": "hello",
"TRANSMISSION_DATE_TIME": "hello",
"TRANSACTION_COUNT": "hello"
}
}

JSON schema does not validate missing properties

Thanks for your help in advance. Its my bad day.
I have the following json and I am trying to figure out the schema for it. Unfortunately, I was stuck at a point with no sign of error.
Please advise the solution
{
"tables_not_to_mask": ["Table_1"],
"tables_to_mask":{
"Table_2": [
{
"column": "BinLogFilename",
"masking_type": "replace_logfilename"
},
{
"column": "ServerId",
"masking_type": "replace_server_id"
}
],
"Table_3": [
{
"column": "BinLogFilename",
"masking_type": "replace_logfilename"
},
{
"column": "ServerId",
"masking_type": "replace_server_id"
}
]
}
}
The Table_1,Table_2,.. are dynamically added. I have created schema that should validate JSON input in the following,
tables_not_to_mask and tables_to_mask are required.
tables_to_mask can have zero or more tables
If there is table in tables_to_mask, it can have zero to many column and masking_type defined.
column and masking_type are mandatory and no one is single.
I created the schema for it and unfortunately, if i remove column or masking_type, the schema does not throw any error.
{
"title": "Schema title",
"description": "Description of the schema",
"type": "object",
"properties": {
"tables_not_to_mask": {
"type": "array",
"minItems": 0,
"items": {"type": "string"}
},
"tables_to_mask": {
"type": "object",
"patternProperties": {
".*": {
"type": "array",
"minItems": 0,
"properties": {
"column": {"type": "string"},
"masking_type": {"type": "string"}
},
"required": [
"masking_type",
"column"
]
}
}
}
},
"required": [
"tables_not_to_mask",
"tables_to_mask"
]
}
Finally, I got the answer. Thanks for looking into it.
{
"$schema": "https://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"tables_not_to_mask":
{"$ref": "#/definitions/tables_not_to_mask_type"},
"tables_to_mask":
{"$ref": "#/definitions/tables_to_mask_type"}
},
"required": [
"tables_not_to_mask",
"tables_to_mask"
],
"definitions": {
"tables_not_to_mask_type": {
"type": "array",
"minItems": 0,
"items": {"type": "string"}
},
"tables_to_mask_type": {
"type": "object",
"patternProperties": {
".*": {"$ref": "#/definitions/tables_type"}
}
},
"tables_type": {
"type": "array",
"minItems": 0,
"items": {
"type": "object",
"properties": {
"column": {"type": "string"},
"masking_type": {"type": "string"}
},
"required": [
"masking_type",
"column"
]
}
}
}
}

How to set JSON validation schema for general properties values

I want to validate JSON using schema (currently draft 6, but we could upgrade if needed). My case is an object with properties whos values all have the same structure, e.g.:
{
"blueFoo": {
"bar1": "someValue",
"bar2": "differentValue"
},
"redFoo": {
"bar1": "someOtherValue",
"bar2": "LoremYpsum"
},
"purpleFoo": {
"bar1": "anotherString",
"bar2": "nextValue"
},
...
}
is there a way to set validation schema for general property values? Something like:
{
"type": "object",
"propertyValue": {
"type": "object",
"required": ["bar1", "bar2"],
"additionalProperties": false,
"properties": {
"bar1": {"type": "string"},
"bar2": {"type": "string"}
}
}
}
Thank you.
additionalProperties is exactly for that purpose:
{
"type": "object",
"additionalProperties": {
"properties": {
"bar1": {"type": "string"},
"bar2": {"type": "string"}
},
"required": ["bar1", "bar2"],
"additionalProperties": false
}
}

Future proof JSON schema

Is it possible to force known objects ("enemy" and "friend") to be defined while other objects are allowed?
I've added the last object {"type": "object"} to display the intended behaviour - but in reality the last object will overrule the two defined objects ("enemy" and "friend") causing any kind of object to be valid with this schema. If I remove the last object, it will allow the two objects but nothing else.
JSON schema (using array for faster testing):
{
"type": "array",
"items": {
"anyOf": [
{"$ref": "#/definitions/friend"},
{"$ref": "#/definitions/enemy"},
{"$ref": "#/definitions/future"}
]
},
"definitions": {
"friend": {
"type": "object",
"properties": {
"time": {"type": "string"},
"value": {"type": "number", "minimum": 100}
},
"required": ["time", "value"],
"additionalProperties": false
},
"enemy": {
"type": "object",
"properties": {
"enemy": {"type": "string"},
"color": {"type": "number"}
},
"required": ["enemy", "color"],
"additionalProperties": false
},
"future": {
"type": "object",
"properties": {
"time": {"type": "string"}
}, "required": ["time"],
"additionalProperties": true
}
}
}
Example JSON (top 3 should be OK, last 3 should not be OK):
[
{"time": "123", "value": 100}, <- should be valid
{"time": "1212", "value": 150}, <- should be valid
{"enemy": "bla", "color": 123}, <- should be valid
{"time": "1212", "value": 50}, <- should be invalid bcoz of "future"
{"enemy": "bla", "color": "123"}, <- shouldn't be valid bcoz of "enemy" schema
{"somethingInFuture": 123, "someFutProp": "ok"} <- should be valid
]
It's not very clear what you really want but you probably want to use a combination of dependencies and minProperties here. The two forms of this keyword are used here: property dependency and schema dependency. You also want at least one property to be defined, hence minProperties. So you can do something like this (note that I also factorized the common schema for color; it may, or may not, be what you want):
{
"type": "object",
"minProperties": 1,
"additionalProperties": {
"type": "string" // All properties not explicitly defined are strings
},
"properties": {
"color": { "type": "number" }
},
"dependencies": {
"enemy": "color", // property dependency
"friend": { // schema dependency
"properties": { "color": { "minimum": 50 } },
"required": [ "color" ]
}
}
}
Note that this schema still allows "enemy" and "friend" at the same time (so does your original schema). To do better, sample JSONs you want to deem valid and invalid should be provided.

Is "type" optional in Json schema

{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Product set",
"type": "array",
"items": {
"title": "Product",
"type": "object",
"properties": {
"id": {
"description": "The unique identifier for a product",
"type": "number"
},
"name": {
"type": "string"
},
"price": {
"type": "number",
"minimum": 0,
"exclusiveMinimum": true
},
"tags": {
"type": "array",
"items": {
"type": "string"
},
"minItems": 1,
"uniqueItems": true
},
"dimensions": {
"type": "object",
"properties": {
"length": {"type": "number"},
"width": {"type": "number"},
"height": {"type": "number"}
},
"required": ["length", "width", "height"]
},
"warehouseLocation": {
"description": "Coordinates of the warehouse with the product",
"$ref": "http://json-schema.org/geo"
}
},
"required": ["id", "name", "price"]
}
}
In the above Json schema "dimensions"is of "type": "object" , is type optional , if "type" is not specified should i assume it to be object. Could not find anything in specs related to optional elements.
https://datatracker.ietf.org/doc/html/draft-zyp-json-schema-03#section-5.1
If the property is not defined or is not in this list, then any
type of value is acceptable.
type is optional, ANY value is acceptable if type is omitted.
I think it is not optional. As you can see in the meta-schema below, the property "type" has no default value:
http://json-schema.org/schema
Also in my JSON Schema library NJsonSchema I set the type to None instead of Object. Check out the TypeRaw property:
https://github.com/rsuter/NJsonSchema/blob/master/NJsonSchema/JsonSchema4.Serialization.cs
When the default value is set to Object a lot more tests from the JSON Schema test suite fail. Maybe you can find a definite answer in this JSON Schema test suite:
https://github.com/json-schema/JSON-Schema-Test-Suite