Validate each JSON node with different JSON schema - json

Im trying to make a system monitor, which is highly customisable by user. This customization is achieved by using JSON file for modeling look of system monitor. The JSON could look like this.
{
"_": "WINDOW",
"name": "myWindow",
"children": [
{
"_": "CPU",
"name": "cpuMonitor",
"freq_Unit": "MHZ"
},
{
"_": "NETWORK",
"name": "network",
"unit": "Kb/s"
},
{
"_": "DISK",
"name": "disk"
}
],
"background": "red"
}
As you can see, each object coresponds to this schema.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"name":"Component",
"type": "object",
"properties":{
"_": {
"type": "string"
},
"name":{
"type":"string"
},
"childern":{
"type":"array"
}
},
"required": ["_","name"]
}
But each component has also its own schema definition. I'd like to parse whole JSON and validate each node for different schema (first if its component and then to its corresponding schema).
I had look at rapidJson and other libraries, but I didnt find solution for validating nodes for different schema. Do you know any library which could do that? Or is it even possible to validate JSON in this way?
All feedback on how to solve this will be appreciated.
Edit: Corrected schema :(

There's a simple approach involved with that, use the oneOf pattern declaration to specify the layout of the array elements. Inside these nested declarations, you specify the fixed identifier (probably the content of your _ field) as a constant, so that there is only one nested schema matching each of your panel types.
Notes:
I had to specify the constant type identifier using the enum specifier because the regular constant specifier didn't work with the library I was using. This may also have been an oversight in the revision of the specification that it was based on.
A different approach is to split the the validation steps. You simply verify that the elements of the array are objects and that they have a string field _ containing one of the supported types. When iterating over the array, you then validate each field individually according to its _ field.

In addition to Ulrich's answer, here's an example of what I'd do:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Component",
"type": "object",
"definitions": {
"base": {
"properties": {
"name": { "type": "string" },
"children": {
"type": "array",
"items": { "$ref": "#" }
}
},
"required": [ "_", "name" ]
},
"cpu": {
"properties": {
"_": { "const": "CPU" },
"freq_Unit": "MHZ"
}
},
"network": {
"properties": {
"_": { "const": "NETWORK" },
"unit": "Kb/s"
}
},
"disk": {
"properties": {
"_": { "const": "DISK" }
}
},
"window": {
"properties": {
"_": { "const": "WINDOW" },
"background": { "enum": [ "red", "orange", "yellow", ... ] }
}
}
},
"allOf": [
{ "$ref": "#/definitions/base" },
{
"oneOf": [
{ "$ref": "#/definitions/cpu" },
{ "$ref": "#/definitions/network" },
{ "$ref": "#/definitions/disk" },
{ "$ref": "#/definitions/window" }
]
}
]
}
First, we require that any instance MUST adhere to base which declares _ and name as required properties. Additionally, we declare a children array property that requires all items also match this schema (giving us a recursive behavior). This doesn't really do much except that it allows us to declare these things in one place instead of having to declare them in the other three definitions.
(Note that we don't declare _ in the properties list. This means that any value will pass for this portion of the schema. We clean it up in the next part. If you want to ensure that future components are declared with strings, then you can add a "type": "string" requirement to that property, but I don't feel it's necessary unless others are authoring those components.)
Second, we declare each of our specific types as separate definitions, using the const keyword to isolate the one we want. This construct is analogous to a switch (or case) statement. If the instance doesn't match one of these explicit options, it fails. If it's missing one of the required base properties, it fails.
This will get you where you want to be.
To take it further, there are two more things you can do:
Add required to the other definitions to say that the specific properties are also required (e.g. freq_Unit for the cpu definition).
Declare each of the definitions in separate files. This would allow you to add a new definition by simply adding a new file and referencing it in the main schema. In my opinion, it's a bit cleaner. Some people prefer to have it all in one file, though.

Related

Structuring JSON schema with hierarchical definitions

I'm new in writing JSON schemas. I thought I could use the following structure in my schema file. What do you think, is this feasible?
{
"properties": {
"my_object": {
"$ref": "#/definitions/my_object"
}
},
"formats": {
"language": {
"type": "string",
"pattern": "^[a-z]{2}-[A-Z]{2}$"
},
"zipcode": {
"type": "string",
"pattern": "\\d{5}-\\d{4}|\\d{5}"
}
},
"definitions": {
"my_object": {
"type": "object",
"properties": {
"language": {"$ref": "#/formats/language"},
"zipcode": {"$ref": "#/formats/zipcode"}
}
}
}
}
The top level only contains references to objects under definition. Since I need some type & pattern pairs more than once I put them under formats.
Is definitions a key word in JSON schema? At least it is widely used in examples. Is it ok to add your own "key words" like formats here or should everything go under definitions?
While a lot of examples indeed use definitions it is outdated and the recommended way is to use $defs instead. Other keys are still supported to be backwards compatible. See here for more
Thus, I'd recommend keeping all sub-schemas in the $defs object. It is still possible to to group semantically related sub-schemas inside $defs so if you insist on keeping the the distinction you can do something like this:
{
"properties":{
"my_object":{
"$ref":"#/$defs/my_object"
}
},
"$defs":{
"my_object":{
"type":"object",
"properties":{
"language":{
"$ref":"#/$defs/formats/language"
},
"zipcode":{
"$ref":"#/$defs/formats/zipcode"
}
}
},
"formats":{
"language":{
"type":"string",
"pattern":"^[a-z]{2}-[A-Z]{2}$"
},
"zipcode":{
"type":"string",
"pattern":"\\d{5}-\\d{4}|\\d{5}"
}
}
}
}

JSON Schema construction for a objects with. common properties but differing in some properties

I have a a number of objects which share a common set of features but differ in one or more properties. The common content is specified as media content in the definitions. I have provided one such object with a 'format' property, but there are other objects, omitted to keep it short, that also have additional properties. Here is a snippet of my attempt at constructing the schema. Is this the correct way to accomplish, this? Many thanks
"definitions": {
"media-content":{
"type": "object",
"title": {
"type": "string"
},
"related-media": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"type": "object",
"properties": {
"type": {
"format": "string",
"enum":["audio", "video"]
},
"category": {
"$ref": "#/definitions/media-content"
}
}
Is this the way to do it?
The first thing that stands out to me is that this isn't valid JSON Schema.
The title keyword provides a title for the schema so it expects a string, but because you've provided a schema, it looks like you're wanting it to be a property. Similarly related-media looks like you expect this to be a property. Are you missing wrapping these in a properties keyword, like you have later for type and category?
These changes would make media-content look like this:
"media-content":{
"type": "object",
"properties": {
"title": {
"type": "string"
},
"related-media": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
}
I have provided one such object with a 'format' property
Again, here, I'm not sure what you're getting at.
"properties": {
"type": {
"format": "string",
"enum":["audio", "video"]
},
"category": {
"$ref": "#/definitions/media-content"
}
}
This says you're expecting type to be a property in your object, but format isn't used right. Although the format keyword has some predefined types and does accept custom values, the way you're using it look like you really want to be using the type keyword. In the end, it doesn't matter because enum will restrict the value to the items you declare in the array ("audio" or "video").
It might be easier to see what you're trying to do if you posted a full minimum exaple.
That said, I'll try to build one to answer the question I think you're asking.
It sounds like you're wanting to build a polymorphic relationship: an inheritance hierarchy where a base type defines some properties and a number of derived types define additional properties.
There's not really a good way to do that with JSON Schema because JSON Schema is a constraints system. That is, you start with {} where anything is valid, and by adding keywords, you're reducing the number of values that are valid.
Still, you might be able to achieve something close by using allOf and $ref.
First, declare the base property set in its own schema. I'd separate them into independent schemas for easier handling. You also need to give it an $id.
{
"$id": "/base-type"
"type": "object",
"properties": {
"base-prop-1": { "type": "string" },
"base-prop-2": { "type": "number" }
}
}
Next, for each of your "derived" schemas, you'll want to create a new schema, each with their own $id value, that references the base schema and declares its own additional requirements.
{
"$id": "/derived-type-1",
"allOf": [
{ "$ref": "/base-type" },
{
"properties": {
"derived-prop": { "type": "boolean" }
}
}
]
}
This second schema requires everything from the /base-type and also requires a derived-prop property that holds a boolean value.

Conditionally determine the required-ness of a field

I need to refer to a sub-schema of certain property (Kind in the example) from a different property in the schema, and then enforce some more conditions on it. Important thing to note is I cannot make those changes where I've defined Kind, I need to refer to it from some other property and then add conditionals on top of it.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"Kind": {
"$id": "#/properties/Kind",
"type": "string",
"enum": [
"Foo",
"Bar"
]
}
},
"allOf": [
{
"if": {
"$ref": "#/properties/Kind",
"const": "Foo"
},
"then": {
"required": [
"MyField"
]
}
}
]
}
A json object like below should fail the validation, because MyField property is absent
{
"Kind": "Foo"
}
I don't want the following solution, since this is just a simplified version and ultimately I want to refer to Kind value from another property. If I do following, then #/properties/Kind is interpreted relative to where I refer Kind so it doesn't refer to the Kind at the top level. I want a solution which uses the $ref and $id keywords.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"Kind": {
"$id": "#/properties/Kind",
"type": "string",
"enum": [
"Foo",
"Bar"
]
}
},
"allOf": [
{
"if": {
"properties": {"Kind":{
"const":"Foo"
}}
},
"then": {
"required": [
"MyField"
]
}
}
]
}
To summarize, let's say I've following JSON structure. The last allOf statement is what I need to add.
- Kind ( enum of One,Two)
- Other
- MyField
- ConditionField
- allOf ( which enforces the required-ness of MyField based on ConditionField)
- allOf ( MyField should be not-required if Kind is One)
[ To add this last conditional, I need to reference the value of Kind.
I'm hoping providing $id to Kind and referring to it with $ref should be my approach,
which doesn't seem to be working]
To summarize even further, I would get my answer if we're able to get the first snippet work using $id and $ref.
There seem to be some misunderstandings that are making it difficult to fully understand the problem here, but one part of the edited question makes enough sense that I think I can start things off and we can iterate on the answer as necessary.
Let's start with some of the things that don't make sense in hopes that it helps clarify possible misunderstandings.
$ref can't change the behavior of a schema. If you can't do something without $ref, then you can't make the schema behave another way by introducing $ref. The only exception to that rule is recursive schemas, which would require an infinitely large and repeating schema without using $ref.
I'm not sure what you are trying to get from $id, but it's pretty safe to say you don't need it for this. In any case, the $id used the question is invalid. An anchor can not have a / in it. Even if it was valid, it would be redundant because you can reference that location with the same JSON Pointer without an anchor.
MyField should be not-required if Kind is One
I'm not sure if "not-required" means forbidden or optional. Everything is optional by default in JSON Schema, so if you meant optional, there is nothing to do here. Therefore, I'll assume for now that you mean forbidden. Here's what that would look like.
{
"type": "object",
"properties": {
"Kind": { "enum": ["One", "Two"] },
"Other": {
"type": "object",
"properties": {
"MyField": {}
}
}
},
"allOf": [
{
"if": {
"properties": {
"Kind": { "const": "One" }
},
"required": ["Kind"]
},
"then": {
"properties": {
"Other": {
"not": { "required": ["MyField"] }
}
}
}
}
]
}

JSONSchema: add extra constraints to property derived from ref

I have a large library of JSONSchemas and I'd like to structure it as follows:
my_object.json # a canonical definition of my_object
create_my_object_response.json # a response to a request to create a particular my_object
In this setup, my_object.json would define a general format of my_object, while create_my_object_response.json would expect some particular values for the fields of my_object. I'd like to structure the create_my_object_response.json schema like this:
{
"type": "object",
"definitions": {},
"$schema": "http://json-schema.org/draft-06/schema#",
"allOf": [
{ "$ref": "my_object.json#" },
{
"type": "object",
"properties": {
"id": { "const": 2 },
"name": { "const": "A Specific Name" }
}
}
]
}
my_object.json contains both the id and name properties but doesn't specify the const values. I tried this setup but it didn't seem to work. How can I represent this? Is it even possible?
I managed to figure this out, so posting the answer here for posterity.
The construction I had was actually right, but instead of "const": 2 I switched to "enum": [2]. I think this had to do with the version of the schema I was using.

Reuse pattern in JSON schema

Is it possible to define a regex once and re-use it? I have a few pretty complex regexes which I would like to use as the pattern for the value of a large number of properties of various different object in my schema. Doing Copy paste of this looks like asking for trouble further down the line, but I can't seem to find a suitable re-use example anywhere.
Cut down schema which illustrates what I want to do.
{
"$schema": "http://json-schema.org/draft-04/schema#",
"patterns": {
"fqdn_or_ipaddress": "(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{1,63}(?<!-)\\.)+[a-zA-Z]{2,63}$)||(((?:^[0-9])(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))(?![0-9])$)|(^\\*$))",
},
"properties": {
"server_hostname" : {
"type":"string",
"pattern": {"#ref", "#/patterns/address"},
},
"proxy_hostname" : {
"type":"string",
"pattern": {"#ref", "#/patterns/address"},
}
}
}
Doesn't validate here http://www.jsonschemavalidator.net/ because "pattern" is not a string. Is this a hole in the re-use. I've looked at patternProperties, but that seems to solve completely different use case.
You can only $ref a schema. You would need to do something like this.
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"server_hostname" : {
"$ref": "#/definitions/fqdn_or_ipaddress",
"description": "The server hostname"
},
"proxy_hostname" : {
"allOf": [{ "$ref": "#/definitions/fqdn_or_ipaddress" }],
"description": "The proxy hostname"
}
},
"definitions": {
"fqdn_or_ipaddress": {
"type": "string",
"pattern": "(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{1,63}(?<!-)\\.)+[a-zA-Z]{2,63}$)||(((?:^[0-9])(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))(?![0-9])$)|(^\\*$))"
}
}
}
EDIT
I added two examples of how to extend from a $ref. In the first, you can just add the description. It will be ignored, but it is not an error. Since description is just a meta-data keyword, this shouldn't be a problem.
In the second example, you can use allOf to wrap the $ref and you can add whatever keywords you need (even non-meta data keywords).
This answer highlights key structure for reusing the defined pattern. However, the example does not validate as:
The definition is given as part of the object reference with "definitions" whereas the "$ref" path is referencing a "patterns" definition that is nowhere to be found.
The pattern definition key is provided as "fqdn_or_ipaddress" whereas the paths in the properties refer to a "fqn_or_ipaddress" which is not defined.
Following is the corrected sample that passes the validation in the JSON schema validator:
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"server_hostname" : {
"$ref": "#/definitions/fqdn_or_ipaddress",
"description": "The server hostname"
},
"proxy_hostname" : {
"allOf": [{ "$ref": "#/definitions/fqdn_or_ipaddress" }],
"description": "The proxy hostname"
}
},
"definitions": {
"fqdn_or_ipaddress": {
"type": "string",
"fqdn_or_ipaddress": "(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{1,63}(?<!-)\\.)+[a-zA-Z]{2,63}$)||(((?:^[0-9])(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))(?![0-9])$)|(^\\*$))"
}
}
}