JSON-Schema pattern repetition inside array - json

Is there a way to create a repetition pattern for elements inside array of a JSON Schema document. I would like to use it in order to validate query produced by a query builder tool.
the part of the schema i currently use is
"complexCondition": {
"anyOf": [
{
"title": "Simple condition, e.x. X==0",
"type": "string"
},
{
"type": "array",
"items": [
{
"$ref": "#/complexCondition"
},
{
"type": "string", "enum": ["AND","OR"]
},
{
"$ref": "#/complexCondition"
}
],
"additionalItems": false
}
]
}
This allows me correct validation of query "conditionA && conditionB && conditionC" as
[[conditionA,"AND",conditionB],"AND",conditionC]
However, I would like to be able and validate documents where query is stored as
[conditionA,"AND",conditionB,"AND",conditionC]
and this to achievable for any number of conditions.

In order to achieve what you intend to do you would need to define a rule for odd and even positions
in an array, which can not be done for arrays of any length with json-schema.
If your expected number of query conditions is not going to grow ad infinitum, you could hard-code the first n positions in items keyword:
"items":[{"$ref":"#/condition},{"$ref":"#/operator"},{"$ref":"#/condition},{"$ref":"#/operator"} ... etc.]
In this case you still have the problem that you could define a wrong condition ending in "operator".
Other option would be to make "oneOf" and build each posibility (this could be automated with a script, I guess you won't have expressions with 100 clauses...)
Anyway I find a bit strange to try to flatten a concept that is inherently recursive.
How would you store this ( (A OR B) OR (C AND B))?
So If you can still rethink your desired serialization schema format I would suggest you to make a recursive approach. Something like:
"predicate" : {
"type" : "array",
"items" : {
"$ref" : "#/clause"
}
}
"clause" : {
"type" : "array",
"items" : [{
"$ref" : "#/condition"
}
],
"additionalItems" : {
"type" : "array",
"items" : [{
"$ref" : "#/operator"
}, {
"$ref" : "#/clause"
}
]
}
}
The generated string is more ugly, but parsing it would be fairly easy with a simple recursive function:
Simple condition:
[["condition1"]]
Simple Clause:
[["condition1",["OR",["condition2"]]]
Adding a clause at the same level
[["condition1",["OR",["condition2"]],["OR",["condition3"]]]
Add a clause to a child level
[["condition1",["OR",["condition2"]],["OR",["condition3", ["AND",["condition4"]]]]]

Related

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"] }
}
}
}
}
]
}

Restrict JSON values to the names of other JSON objects

I'd like to use JSON schema to validate some values. I two objects, call them trackedItems and trackedItemGroups. The trackedItemGroups are a group name and a list of trackedItems names. For example, the schema is similar to:
"TrackedItems": {
"type": "array",
"items": {
"type": "object",
"properties": {
"TrackedItemName": { "type": "string" },
"Properties": { ---- }
}
}
},
"TrackedItemGroups": {
"type": "array",
"items": {
"type": "object",
"properties": {
"GroupName": {
"type": "string"
},
"TrackedItems": {
"type": "array",
"items": {"type": "string"}
}
}
}
}
I'd like to validate that every string in a TrackedItemGroups's TrackedItems array is a name that's been defined in TrackedItems.TrackedItemName.
This would be something like using the enum property to restrict the values, but the enum list is generated based on the values in TrackedITems.TrackedItemName.
How can I write the schema to use the JSON's own data for validation?
I'm aware I could move things around, i.e. the TrackedItems define the group they're in, but there are hundreds of tracked items and this organization works much better for my use case.
I've tried this:
"TrackedItems": {
"type": "array",
"items": {
"oneOf": [
{"$ref":"#/properties/TrackedItems/items/properties/TrackedItemName"}
]
}
}
But this results in an error:
Newtonsoft.Json.Schema.JSchemaReaderException: Could not resolve
schema reference
'#/properties/TrackedItems/items/properties/TrackedItemName'.
For a data example, if I had the TrackedItems:
Item1, Item2, ItemA, ItemB, ItemC
And groups:
Group1:
Item1, ItemB, ItemC
Group2:
Item1, Item2, ItemZ
Group2 would throw a violation because it contains an item not defined in TrackedItems.
Being a vocabulary for validation (and certain other things described by trivial assertions), JSON Schema does not provide a way to verify the consistency of data.
Validation means assertions like "Verify that X is a string."
Consistency means things like "Verify that X is the ID of an existing, active user."
Since data being compared might be in another database altogether, and since these sorts of assertions are non-trivial, JSON Schema leaves verifying the consistency of data up to the application and/or other technologies. Some implementations have vendor-specific extensions for intra-document comparisons, however these are not standardized, and I'm not aware of any that would work here.
A $ref reference doesn't work here, as it's just a way to substitute in another schema by reference. If you can manage to get the reference to work (and I'm not sure why you got an error, this is implementation-specific detail), this schema:
{ "oneOf": [
{"$ref":"#/properties/TrackedItems/items/properties/TrackedItemName"}
] }
Is the exact same thing as saying:
{ "oneOf": [
{"type": "string"}
] }
Since you're asking "verify that one of the following one statements is true", this is also the same as simply:
{"type": "string"}
This is not to say you can't declare relationships between data in JSON using JSON Schema, but JSON Schema is somewhat opinionated about using URIs and hyperlinks to do so.

JSON schema for payloads accepted vs objects returned

I'm fleshing out schemas for a RESTful web service, and I'm a bit stumped on one small thing. Imagine if I have the following schema:
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"required": ["name"],
"properties": {
"name": {
"type": "string"
},
"urn": {
"type": "string"
}
}
}
Since the URN is generated by my service, I don't want to accept it in the client request. So urn is not included in the required array. However, it IS required in the response, so I can't use this schema to validate the response my service gives. I'd prefer not to have to use two different schemas and have to keep them in sync.
Is there a way to use a single schema to strictly model both cases? Or, if I need to use two schemas, is there a way to reference a common structural schema and just override the required field from my request and response schemas?
This is a known problem and there is no good way to handle it.
The only way to keep it to one schema is to not include the server generated properties in the required array and do additional checks on the server side to validate those properties.
No, there is no way to override a schema keyword. JSON Schema keywords always adds constraints to the set. You need to start with the common schema and extend from there using allOf.
Here is an example of the kind of thing you will need to do.
Creation Schema:
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "http://example.com/create-my-schema",
"type": "object",
"required": ["name"]
"properties": {
"name": {
"type": "string"
}
}
}
Full Schema:
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "http://example.com/my-schema",
"allOf": [{ "$ref": "http://example.com/create-my-schema" }],
"required": ["urn"],
"properties": {
"urn": {
"type": "string"
}
}
}
If you don't care about the human readability of the schemas, this approach is fine. Otherwise, some people have opted to dynamically build schemas on the server-side so the resulting schemas may have duplication, but the code doesn't.
You can use dynamic schema with the use of "oneOf"
{
"type" : "object",
"required" : ["name"],
"properties" : {
"name" : {
"oneOf" : [{
"$ref" : "#/definitions/withURN"
}, {
"$ref" : "#/definitions/withoutURN"
}
]
}
},
"definitions" : {
"withURN" : {
"properties" : {
"name" : {
"type" : "string"
},
"urn" : {
"type" : "string"
}
}
},
"withoutURN" : {
"properties" : {
"name" : {
"type" : "string"
}
}
}
}
}
for some example have a look: http://json-schema.org/example2.html
also this discussion thread: How to use dependencies in JSON schema (draft-04)

How to validate string and number using json schema

I would like to validate a schema based on either its maximum/minimum (number) OR maximumLength/minimumLength (string).
I have a json form:
[
{
"key":"foo",
"title":"Test",
"type":"string"
}
]
and a json schema:
{
"type": "object",
"properties": {
"foo": {
"type": ["number","string"],
"maxLength":2,
"minLength":0,
"minimum":3,
"maximum":10
}
}
}
and a json model:
{
"foo": "bar"
}
Why does this example not work with validation? The model I have is not validated to false. According to this document it is possible to have different types defined in an array, but how can we do validation based on min/max values?
Your schema is correct. The validator you are using doesn't work properly. Here is an alternative that uses anyOf instead.
{
"type": "object",
"properties": {
"foo": {
"anyOf": [
{ "$ref": "#/definitions/boundedNumber" }
{ "$ref": "#/definitions/boundedString" }
]
}
},
"definitions": {
"boundedString": {
"type": "string",
"maxLength": 2,
"minLength": 0
},
"boundedNumber": {
"type": "number",
"minimum": 3,
"maximum": 10
}
}
}
Although it is quite a bit longer, some would argue that this is actually easier to read/maintain because of the separation of the type specific keywords.
Your schema is validating JSON objects ("type":"object"). In addition, if they have a property with key "foo", its value must be either a number between 3 an 10, or a string of maximum length 2.
Valid objects according to your schema:
{"foo":6}
{"foo":"as"}
Invalid objects:
{"foo":60}
{"foo":"asereje"}
If you want to validate arrays you must define your parent object as an array and use items tag to specify the schema for the array items, for instance:
{
"type" : "array",
"items" : {
"type" : "object",
"properties" : {
"foo" : {
"type" : ["number", "string"],
"maxLength" : 2,
"minLength" : 0,
"minimum" : 3,
"maximum" : 10
}
}
}
}
The schema above would validate the following JSON array:
[{
"foo" : 6
}, {
"foo" : "as"
}
]
John the issue is the type "type" : ["number", "string"]
Angular Schema Form is generating fields from the combined JSON Schema and the UI Schema (form), when the field type is defined it knows which type to validate against, when there are multiple types it doesn't know which part of the spec to base the form field against, so it falls back to a textbox and does not add the appropriate validation requirements for string alone.
To achieve your desired outcome you would need to tell it which field type you wish to use. There is a bug in the 0.8.x versions where it does not validate based on the type set in the UI schema if it cannot determine the type in the data schema, I believe that is fixed in the latest development branch. If not if there is an issue raised in Git, I would prioritise it.