I'd like to define an object something like this, to map to something like a java Map (but not exactly, since this needs to interface with typescript as well). Ultimately, what I'm trying to figure out is how to declare the <T> type of something (say Dict<MyObj>) in another object (if this is even possible):
(this is obviously not a valid json schema)
{
"type": "object",
"javaType": "some.package.base.DictKeyValuePair",
"properties": {
"key": { "type": "string" },
"value": { "type": "<T>" }
}
}
which would be referenced in other json schemas something like this:
{
"type": "object",
"javaType": "some.package.base.Dict",
"properties": {
"key": { "type": "string" },
"value": { "type": "<T>" },
"keValuePairs": {
"type": "array",
"items": {
"$ref": "DictKeyValuePair.json",
"genericType": "<T>"
}
}
}
}
finally being used in an object similar to this:
{
"type": "object",
"javaType": "some.package.SomeObject",
"properties": {
"someDict": {
"$ref": "Dict.json",
"genericType": "SomeObjectOrSimpleType"
}
}
}
So... is this possible in a json schema?
It is possible to do something like that using json-schema but you need to define the schemas, take a look to this samples:
http://json-schema.org/learn/examples/address.schema.json
{
"$id":"https://example.com/address.schema.json",
"$schema":"http://json-schema.org/draft-07/schema#",
"description":"An address similar to http://microformats.org/wiki/h-card",
"type":"object",
"properties":{
"post-office-box":{
"type":"string"
},
"extended-address":{
"type":"string"
},
"street-address":{
"type":"string"
},
"locality":{
"type":"string"
},
"region":{
"type":"string"
},
"postal-code":{
"type":"string"
},
"country-name":{
"type":"string"
}
},
"required":[
"locality",
"region",
"country-name"
],
"dependencies":{
"post-office-box":[
"street-address"
],
"extended-address":[
"street-address"
]
}
}
Related
Given the following json document:
{
"property-1": {
"server": "string-value",
"environment": "string-value",
"cluster": "string-value"
},
"property-2": {
"server": "string-value",
"environment": "string-value",
"cluster": "string-value"
}
}
I am busy doing up a json schema for this type of json document where property-1 can be any string, but inside of that, we will have a set structure with a number of required. What I am struggling with is how to define the schema so that the user will be able to set any string for property-1 | property-2.
I assume I have to use a pattern, but what I have tried haven't worked that well.
{
"$schema": "http://json-schema.org/draft/2020-12/schema",
"type": "object",
"definitions": {
"role": {
"$id": "#svcrole",
"description": "Application name for which role is required.",
"type": "object",
"pattern": "^[a-zA-Z\\-]+$",
"properties": {
"server": {
"type": "string"
},
"environment": {
"type": "string"
},
"cluster": {
"type": "string"
}
},
"required": [
"server",
"environment",
"cluster"
]
}
},
"properties": {
"$schema": {
"type": "string"
},
"$name": {
"$ref": "#/definitions/role"
}
}
}
So I stumbled upon the solution here.
You want to use propertyNames and patternProperties like:
"propertyNames": {
"pattern": "^[a-zA-Z\\-]+$"
},
"patternProperties": {
"": {
"$ref": "#/definitions/role"
}
}
That will then allow you to use a pattern to define the property name and have it all dynamic.
In plain English, I want a flexible schema that will allow an object called "message" to contain a couple of string props, and then a 3rd prop that can be either a plain string or another object. So, if it's defined thusly, is this achieving the goal or is there a validation gotcha that I am missing?
"message": {
"type": "object",
"properties": {
"another": {
"$ref": "another.schema.json"
},
"#type": {
"type": "string",
"enum": [
"One",
"Two",
"Three"
]
}
},
"allOf": [
{
"$ref": "#message"
},
{
"anyOf": [
{
"plainolstring": {
"type": "string"
}
},
{
"obj": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"type": {
"type": "string"
}
}
}
}
]
}
]
}
}
And that would validate against either
"message": {
"another": "blah",
"#type": "foo",
"plainolstring": "sdfSR345w34"
}
or
"message": {
"another": "blah",
"#type": "foo",
"obj": {
"id":"sdfSR345w34",
"type": "guid"
}
}
Validating here against Schema v7 says that it is valid, but while it may be syntactically correct, would it achieve what I want?
Yes, anyOf will do what you want - except you have a syntax error by wrapping the subschemas with those properties "plainolstring" and "obj". The correct form would be:
{
"anyOf": [
{
"type": "string"
},
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"type": {
"type": "string"
}
}
}
]
}
Moreover, you don't even need the anyOf -- you can simplify that subschema to:
{
"type": ["string", "object"],
"properties": {
"id": {
"type": "string"
},
"type": {
"type": "string"
}
}
}
The properties keyword itself does not enforce that the type must be an object -- you need to use type for that. properties just says "if this is an object, this is the definition of those properties".
{
"policyHolder": {
"fullName": "A"
},
"traveller": [
{
"fullName": "B",
"relationship": "Spouse"
},
{
"fullName": "A",
"relationship": "My Self"
}
]
}
In above json, I want to validate that
if "relationship" = "My Self" then fullName must match the fullName in policyHolder
A field relationship must exist in traveller array, else json is invalid
I have tried to create a json schema with if-else, allOf, etc. but nothing works which can do these validations but not able to.
Please help!!
Schema:
{
"type": "object",
"required": [
"policyHolder",
"traveller",
],
"properties": {
"policyHolder": {
"$id": "#/properties/policyHolder",
"type": "object",
"required": [
"fullName"
],
"properties": {
"fullName": {
"$id": "#/properties/policyHolder/properties/fullName",
"type": "string",
}
}
},
"traveller": {
"$id": "#/properties/traveller",
"type": "array",
"minItems": 1,
"items": {
"$id": "#/properties/traveller/items",
"type": "object",
"properties": {
"fullName": {
"$ref": "#/properties/policyHolder/properties/fullName"
},
"relationship": {
"$id": "#/properties/traveller/items/properties/relationship",
"type": "string",
}
},
"required": [
"fullName",
"relationship"
],
}
}
}
}```
It's your first requirement that you're going to have the most trouble with. JSON Schema doesn't support validation of data against data elsewhere in the instance. It's a highly discussed topic, but nothing has been adopted yet. I suggest you verify this with a little code.
For the second, I would suggest you extract some of your subschemas into definitions rather than trying to muck about with IDs. IDs are typically more beneficial if you're referencing them from other documents or if you use short (like single-word) IDs. Defining the ID as its location in the document is redundant; most processors will handle this automatically.
{
"type": "object",
"required": [
"policyHolder",
"traveller",
],
"definitions": {
"person": {
"type": "object"
"properties": {
"fullName": {"type": "string"}
},
"required": ["fullName"]
},
"relationship": { "enum": [ ... ] } // list possible relationships
},
"properties": {
"policyHolder": { "$ref": "#/definitions/person" },
"traveller": {
"type": "array",
"minItems": 1,
"items": {
"allOf": [
{ "$ref": "#/definitions/person" },
{
"properties": {
"relationship": { "$ref": "#/definitions/relationship" }
},
"required": ["relationship"]
}
]
}
}
}
}
(I extracted the relationship into its own enum definition, but this is really optional. You can leave it inline, or even an unrestricted string if you don't have a defined set of relationships.)
This can't currently be done with JSON Schema. All JSON Schema keywords can only operate on one value at a time. There's a proposal for adding a $data keyword that would enable doing this kind of validation, but I don't think it's likely to be adopted. $data would work like $ref except it references the JSON being validated rather than referencing the schema.
Here's what how you would solve your problem with $data.
{
"type": "object",
"properties": {
"policyHolder": {
"type": "object",
"properties": {
"fullName": { "type": "string" }
}
},
"traveler": {
"type": "array",
"items": {
"type": "object",
"properties": {
"fullName": { "type": "string" },
"relationship": { "type": "string" }
},
"if": {
"properties": {
"relationship": { "const": "My Self" }
}
},
"then": {
"properties": {
"fullName": { "const": { "$data": "#/policyHolder/fullName" } }
}
}
}
}
}
}
Without $data, you will have to do this validation in code or change your data structure so that it isn't necessary.
I have a json object like:
{
"session": {
"session_id": "A",
"start_timestamp": 1535619633301
},
"sdk": {
"name": "android",
"version": "21"
}
}
The sdk name can either be android or ios. And the session_id is based on name field in sdk json. I have written a json schema using conditional statement (Using draft 7) as follows:
But it works in an unexpected manner:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$ref": "#/definitions/Base",
"definitions": {
"Base": {
"type": "object",
"additionalProperties": false,
"properties": {
"session": {
"$ref": "#/definitions/Session"
},
"sdk": {
"$ref": "#/definitions/SDK"
}
},
"title": "Base"
},
"Session": {
"type": "object",
"additionalProperties": false,
"properties": {
"start_timestamp": {
"type": "integer",
"minimum": 0
},
"session_id": {
"type": "string",
"if": {
"SDK": {
"properties": {
"name": {
"enum": "ios"
}
}
}
},
"then": {
"pattern": "A"
},
"else": {
"pattern": "B"
}
}
},
"required": [
"session_id",
"start_timestamp"
],
"title": "Session"
},
"SDK": {
"type": "object",
"additionalProperties": false,
"properties": {
"version": {
"type": "string"
},
"name": {
"type": "string",
"enum": [
"ios",
"android"
]
}
},
"required": [
"name",
"version"
],
"title": "SDK"
}
}
}
So the following JSON Passes:
{
"session": {
"session_id": "A",
"start_timestamp": 1535619633301
},
"sdk": {
"name": "ios",
"version": "21"
}
}
But this fails:
{
"session": {
"session_id": "B",
"start_timestamp": 1535619633301
},
"sdk": {
"name": "android",
"version": "21"
}
}
can someone explain y?.. Even this passes:
{
"session": {
"session_id": "A",
"start_timestamp": 1535619633301
},
"sdk": {
"name": "android",
"version": "21"
}
}
I think you're having a similar problem as in this question.
#Relequestual is right in that you need the properties keyword around your SDK callout. But for what you want to do, you need to reorganize.
Subschemas only operate on their level in the instance, not at the root.
Consider this schema for a simple JSON object instance containing a one and a two property:
{
"properties": {
"one": {
"enum": ["yes", "no", "maybe"]
},
"two": {
"if": {
"properties": {
"one": {"const": "yes"}
}
},
"then": {
... // do some assertions on the two property here
},
"else": {
...
}
}
}
}
The if keyword under the two property can only consider the portion of the instance under the two property (i.e. two's value). It's not looking at the root of the instance, so it can't see the one property at all.
To make it so that the subschema under the two property subschema can see the one property in the instance, you have to move the if outside of the properties keyword.
{
"if": {
"properties": {
"one": {"const" : "yes"}
}
},
"then": {
... // do some assertions on the two property here
},
"else": {
... // assert two here, or have another if/then/else structure to test the one property some more
}
}
For two possible values of one, this is pretty good. Even three possible values isn't bad. However, as the possible values of one increases, so does the nesting of ifs, which can make your schema horrible to read (and possibly make validation slower).
Instead of using the if/then/else construct, I suggest using an anyOf or oneOf where each subschema represents a valid state for the instance, given the varying values of one.
{
"oneOf": [
{
"properties": {
"one": {"const": "yes"},
"two": ... // do some assertions on the two property here
}
},
{
"properties": {
"one": {"const": "no"},
"two": ... // do some assertions on the two property here
}
},
{
"properties": {
"one": {"const": "maybe"},
"two": ... // do some assertions on the two property here
}
}
]
}
This is much cleaner in my opinion.
Hopefully that explanation helps you reconstruct your schema to allow those other instances to pass.
You have to move your conditional to a high enough level to be able to reference all of the the properties it needs to reference. In this case, that's the /definitions/Base schema. Then you just need to write your schemas properly as Relequestual explained.
{
"$ref": "#/definitions/Base",
"definitions": {
"Base": {
"type": "object",
"properties": {
"session": { "$ref": "#/definitions/Session" },
"sdk": { "$ref": "#/definitions/SDK" }
},
"allOf": [
{
"if": {
"properties": {
"sdk": {
"properties": {
"name": { "const": "ios" }
}
}
},
"required": ["sdk"]
},
"then": {
"properties": {
"session": {
"properties": {
"session_id": { "pattern": "A" }
}
}
}
},
"else": {
"properties": {
"session": {
"properties": {
"session_id": { "pattern": "B" }
}
}
}
}
}
]
},
...
}
The value of if must be a JSON Schema. If you were to take lines https://gist.github.com/Relequestual/f225c34f6becba09a2bcaa66205f47f3#file-schema-json-L29-L35 (29-35) and use that as a JSON Schema by itself, you would impose no validation constraints, because there are no JSON Schema key words at the top level of the object.
{
"SDK": {
"properties": {
"name": {
"enum": "ios"
}
}
}
}
This is allowed in the specification, because people may want to extend the functionality of JSON Schema by adding their own key words. So it's "valid" JSON Schema, but doesn't actually DO anything.
You Need to add properties to the schema for it to make sense.
{
"properties": {
"SDK": {
"properties": {
"name": {
"const": "ios"
}
}
}
}
}
Additionally, enum must be an array. When you only have a single item, you may use const.
I have a JSON schema I want to change by a dependency of one of the values of the JSON structure. For example if {"foo":1} also include {"fooBar":"number"} in the schema, resulting in {"foo":"number", "fooBar":"number"} but if {"foo":2} instead include {"fooBar2":"bool", "fooBar3":"string"} resulting in {"foo":1, "fooBar2":"bool", "fooBar3":"string"}. Is this possible.
I know how to make the inclusion of a key change the schema (code example from here) but I cannot find any example on how this could be done using values. If this is even possible.
{
"type": "object",
"properties": {
"name": { "type": "string" },
"credit_card": { "type": "number" },
"billing_address": { "type": "string" }
},
"required": ["name"],
"dependencies": {
"credit_card": ["billing_address"]
}
}
It can be done, but it's a little complicated. Below is the general pattern.
{
"type": "object",
"anyOf": [
{
"properties": {
"foo": { "enum": [1] },
"fooBar": { "type": "number" }
}
},
{
"properties": {
"foo": { "enum": [2] },
"fooBar2": { "type": "boolean" },
"fooBar3": { "type": "string" }
}
}
]
}