Is there a standard approach to specifying a property to be a dictionary or map keyed by string with a value type T specified somewhere else in the schema?
For example, suppose you want to model a user's favorite movies where the key type is the name of the movie and the value type is some set of attributes about the movie (year made, budget, gross income, etc.)
I imagine you could model first a MovieDataPair as a type with name property and a value property containing the desired attributes. Then the map would be an array of those. But, then you would need a special unique constraint that ensured any movie name only appeared once.
Is there something in json schema to support this, or a standard pattern used for it?
If not built in support in json schema, what about other schema solutions?
After some study I've come up with the following answer:
The best way to see this in action is to find some examples. It
happens that there are several examples of this in the draft04 schema
itself (definitions, properties, patternProperties,...) and they
usually follow the same pattern.
For example, the definitions property of the draft04 schema defines what
should appear in a schema at the definitions property. Here is the
subschema associated with the definitions property:
"definitions": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"default": {}
},
This says the entry at "#/definitions/" must be an object. The fact
that it is a json object means it will have unique keys itself. Now
for the values in the object, that is what additionalProperties is
designed to describe. In this case it says that the value of each
property must itself conform to the root of the schema "#". What this
means is that each value in the definitions property object of a valid json schema
object must also be a schema.
If this were typed like C++ it might look like:
std::map< std::string, Schema > definitions;
Effectively a map with a string key can be thought of as like a json
object with a structured value type. So, to create your own:
std::map< std::string, T >
First define the schema for T. For example:
"definitions" : {
"movie" : {
"properties": {
"title" : { "type" : "string" },
"year_made" : { "type" : "integer" },
"rating" : { "type" : "integer" }
}
}
}
For the value type T stored, decide if you want to allow any
properties, as long as these specified properties are typed as
specified above. If you only want these properties, add
"additionalProperties" : false
"definitions" : {
"movie" : {
"additionalProperties" : false,
"properties": {
"title" : { "type" : "string" },
"year_made" : { "type" : "integer" },
"rating" : { "type" : "integer" }
}
}
}
Also decide if you actually require all of the properties to be
present for the movie to be valid. If so, add a required entry.
"definitions" : {
"movie" : {
"additionalProperties": false,
"required" : [ "title", "year_made", "rating" ],
"properties": {
"title" : { "type" : "string" },
"year_made" : { "type" : "integer" },
"rating" : { "type" : "integer" }
}
},
Now the shape T for movie is defined. Create a definition for
the collection, or map of movies referencing the movie schema
defined as was done by definitions in the draft schema. Note: in
the "movie_map" additionalProperties has a different meaning than
that of "movie". In the case of "movie" it is a boolean false
which indicates no additional properties beyond what is listed in
properties. In the case of "movie_map" it means - if there are
additional properties, they must look like this schema. But,
since no properties have been specified in movie_map it really means
all properties in the object instance must conform to #/definitions/movie. Now all
values in a "movie_map" will look like the defined movie schema.
{
"definitions" : {
"movie" : {
"additionalProperties": false,
"required" : [ "title", "year_made", "rating" ],
"properties": {
"title" : { "type" : "string" },
"year_made" : { "type" : "integer" },
"rating" : { "type" : "integer" }
}
},
"movie_map" : {
"type": "object",
"additionalProperties": { "$ref": "#/definitions/movie" },
"default": {}
}
}
}
Now use the defined schema movie_map somewhere within the schema:
{
"title" : "movie data",
"additionalProperties" : false,
"required" : [ "movies" ],
"properties" : {
"movies" : { "$ref" : "#/definitions/movie_map" }
},
"definitions" : {
"movie" : {
"additionalProperties": false,
"required" : [ "title", "year_made", "rating" ],
"properties": {
"title" : { "type" : "string" },
"year_made" : { "type" : "integer" },
"rating" : { "type" : "integer" }
}
},
"movie_map" : {
"type": "object",
"additionalProperties": { "$ref": "#/definitions/movie" },
"default": {}
}
}
}
Here is a sample object, which can be thought of as a map, of movies
that validates against the schema:
{
"movies" : {
"the mission" : {
"title":"The Mission",
"year_made":1986,
"rating":5
},
"troll 2" : {
"title":"Troll 2",
"year_made":1990,
"rating":2
}
}
}
If I wanted to model a structure for users favorites movies (remind Json Schema is intended for structure validation) I would make something like:
{
"description":"moviesFan",
"properties": [
"favoriteMovies": {
"type":"array",
"uniqueItems":True
"allOf": [{ "$ref": "#/definitions/movie" }]
}
],
"definitions": {
"movie": {
"type": "object",
"properties": {
"yearMade": {}
...
}
}
}
Does it make sense to you?
Here's my way to support for map. Hope to help.
{
"type": "object",
"title": "map data",
"required": [
"map"
],
"properties": {
"sOnePurRecord": {
"title": "map",
"additionalProperties": false,
"properties": {
"mapItem": {
"type": "object",
"maxProperties": 10,
"minProperties": 1,
"patternProperties": {
"^[a-zA-Z0-9]{5,20}$": {
"$ref": "#/definitions/value"
}
},
"additionalProperties": {
"$ref": "#/definitions/value"
}
}
},
"required": [
"mapItem"
]
}
},
"definitions": {
"value": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"id": {
"type": "integer"
}
}
}
}
}
Related
I'm trying to validate a required property with if statement in JsonSchema, but it isn't informing me the property's error in detail.
The validation has been made correctly, but the error don't specify the property and which validation failed.
It works for required properties in the root level of an object, but when I specify a required property inside an object, it just warn that the json doesn't match and specify the then or else schema's path.
Schema example:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": [
"name",
"partners"
],
"properties": {
"partners": {
"type": "array",
"items": {
"type": "object",
"if": {
"properties": {
"juridical": {
"type": "object"
},
"natural": {
"type": "null"
}
}
},
"then": {
"properties": {
"juridical": {
"required": [ "tradeName" ],
"properties": {
"tradeName": {
"type": "string"
}
}
}
}
},
"else": {
"properties": {
"natural": {
"required": ["name"],
"properties": {
"name": {
"type": "string"
}
}
}
}
}
}
}
}
}
Json example:
{
"name": "Joao",
"partners": [
{
"juridical": null,
"natural": {
}
},
{
"juridical": {
"tradeName": ""
},
"natural": null
}
]
}
It should warns that the first partner have the "name" required (Required properties are missing from object: name.), instead it only tell me: JSON does not match schema from 'else'..
With a simple schema like this it works as expected:
Schema example:
{
"if": { "properties": { "power": { "minimum": 9000 } } },
"then": { "required": [ "disbelief" ] },
"else": { "required": [ "confidence" ] }
}
Json example:
{ "power": 10000 }
I'm using JsonSchemaValidator.net to verify the results.
Basically, JSON document either validates against JSON schema or not. This logic goes down through all sub-schemas and conditionals.
The content of error messages depends on specific implementation of JSON Schema validator. The one you use comes from a specific provider. As pointed by Relequestal, you cannot expect specific type of error message handling from specific implementation, unless it's what the provider documentation describes.
How about filing a suggestion to authors of the validator you use about extending messages for if-then-else case, feeding in your case?
Alternative approach: As I understand, your goal is to get as much specific error information as possible with this specific validator. It is what it is, so an alternative schema might fit the goal. As JSON Schema itself is a JSON document, thus you may consider a workaround by naming nodes in Schema in some consistent manner and using one of logical operators ("anyOf" (logical OR), "allOf" (logical AND), "oneOf" (logical XOR) ) instead of if-then-else.
Please note: schema based validator, in case of "allOf", "anyOf", "oneOf" is expected to run through all schemas until the logical condition is satisfied.
"allOf" - will check always if JSON doc validates against all schemas
(AND)
"anyOf" - will check if JSON doc validates at least against 1 schema
(OR, so some validator implementations might stop checking after
first positive result as it's sufficient to evaluate check against
"anyOf" to true),
"oneOf" - will check always if JSON doc validates exactly against
one of enlisted schemas by checking against all of them (XOR)
(see: https://json-schema.org/draft-07/json-schema-validation.html#rfc.section.6.7.1 )
Thus if validated instance doesn't match schemas in above cases, the validator implementation may produce some 'false positives' in terms of error messages, as it will enlist issues encountered vs all schemas. It simply can't read our minds and guess what we meant by providing specific JSON doc, so it throws all on us to judge and decide.
One of many solutions could be to define variants of "juridical" and "natural" and combine them logically into schemas as in your case it seems you expect:
either juridical is an object (+relevant constraints) AND natural is not an object or juridical is not an object and natural is an object
(+relevant constraints).
Alternative schema (please note the "examples" section containing some test JSON documents):
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": [
"name",
"partners"
],
"properties": {
"partners": {
"type": "array",
"items": {
"type": "object",
"anyOf" : [
{"$ref" : "#/definitions/juridical-is-object-and-natural-is-null"},
{"$ref" : "#/definitions/juridical-is-not-an-object-and-natural-is-an-object"}
],
"required" : ["natural","juridical"]
}
}
},
"examples" : [
{
"name": "Joao",
"partners": [
{
"juridical": null,
"natural": {
}
},
{
"juridical": {
"tradeName": ""
},
"natural": null
}
]
},
{
"name": "Joao",
"partners": [
{
"juridical": null,
"natural": {
"name" : ""
}
},
{
"juridical": {
"tradeName": ""
},
"natural": null
}
]
},
{
"name": "Joao",
"partners": [
{
"juridical": null,
"natural": {
}
},
{
"juridical": {
"tradeName": ""
},
"natural": null
},
{
"juridical" : [],
"natural" : {}
}
]
}
],
"definitions" : {
"natural" : {
"is-object" : {
"type" : "object",
"required": ["name"],
"properties": {
"name": {
"type": "string"
}
}
},
"is-not-an-object" : {
"not" : { "type" : "object" }
},
},
"juridical" : {
"is-object" : {
"type" : "object",
"required": ["tradeName"],
"properties": {
"name": {
"type": "string"
}
}
},
"is-not-an-object" : {
"not" : { "type" : "object" }
},
},
"juridical-is-object-and-natural-is-null" : {
"properties" : {
"natural" : {"$ref" : "#/definitions/natural/is-not-an-object"},
"juridical" : {"$ref" : "#/definitions/juridical/is-object"}
},
},
"juridical-is-not-an-object-and-natural-is-an-object" : {
"properties" : {
"natural" : {"$ref" : "#/definitions/natural/is-object"},
"juridical" : {"$ref" : "#/definitions/juridical/is-not-an-object"}
}
},
}
}
Notes:
"not" : { schema } error message might be confusing for casual users, but it conforms to the spec: https://json-schema.org/draft-07/json-schema-validation.html#rfc.section.6.7.4
Update
As explained in comments, you are after error details. Given the constraints of the selected tool in terms of if-then-else error details for more complex schemas, did you try to reshape schema using different keywords to trigger as less overhead messages as possible?
Alternative schema 2
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": [
"name",
"partners"
],
"properties": {
"partners": {
"type": "array",
"items" : {
"properties" : {
"natural" : {
"if" : { "type" : "object" },
"then" : { "required" : ["name"] },
"dependencies" : {
"name" : {
"properties" : {
"name" : {"type" : "string"}
}
}
}
},
"juridical" : {
"if" : { "type" : "object" },
"then" : { "required" : ["tradeName"] },
"dependencies" : {
"tradeName" : {
"propertyNames" : {
"enum" : ["tradeName"]
},
"properties" : {
"tradeName" : {"type" : "string"}
}
}
}
}
},
"anyOf" : [
{"$ref" : "#/definitions/natural-is-null-juridical-is-an-object"},
{"$ref" : "#/definitions/natural-is-an-object-juridical-is-null"}
]
}
}
},
"definitions" : {
"natural-is-null-juridical-is-an-object" : {
"properties" : {
"natural" : { "type": "null"},
"juridical" : { "type" : "object"}
}
},
"natural-is-an-object-juridical-is-null" : {
"properties" : {
"natural" : { "type": "object"},
"juridical" : { "type" : "null"}
}
},
},
"examples" : [
{
"name": "Joao",
"partners": [
{
"juridical": null,
"natural": {
}
},
{
"juridical": {
"tradeName": "",
},
"natural": null
},
{
"juridical" : {},
"natural" : {}
},
{
"juridical" : null,
"natural" : null
}
]
},
]
}
I'm trying to write a json schema that searches any json with different structures for all occurences of a certain property called "field_name" and checks if that property has a value. There can't be an empty "field_name".
The property "field_name" can be at any level in the json file, e.g
https://raw.githubusercontent.com/stopopol/deims_apps/master/metadata_models/smm.json
So far I have this, but it never complains when a "field_name" is empty.
{
"$schema": "http://json-schema.org/schema#",
"title": "Metadata Model",
"type": "object",
"required": [
"name",
"abbreviation",
"version",
"releaseDate",
"scope",
"content"
],
"patternProperties": {
"field_name": {
"type": "string",
"minLength": 1
}
}
}
I thought that I could just check for any occurence of property "field_name" and that it needs to be a string with a lenght of at least 1.
You can do this with a surprisingly simple recursive schema. The properties and additionalProperties keywords only apply when the data being validated is an object. If the data is not an object, these keywords get ignored. This allows us to express the "if the value is an object" part simply by leaving out the "type": "object" declaration.
The use of allOf/definitions shows how to express the recursive constraint without making the entire schema recursive.
{
"title": "Metadata Model",
"type": "object",
"required": [
"name",
"abbreviation",
"version",
"releaseDate",
"scope",
"content"
],
"allOf": [{ "$ref": "#/definitions/field_name-not-empty-deep" }],
"definitions": {
"field_name-not-empty-deep": {
"properties": {
"field_name": {
"type": "string",
"minLength": 1
}
},
"additionalProperties": { "$ref": "#/definitions/field_name-not-empty-deep" }
}
}
}
{
"anyOf" :
[
{
"not" :
{
"type" : "object"
}
},
{
"properties" :
{
"field_name" :
{
"not" :
{
"type" : "null"
}
}
},
"additionalProperties" :
{
"$ref" : "#"
}
}
]
}
Each instance encountered is either not an object OR it checks for your property not being null, then it runs the filter on all other properties (this is done using the $ref which points to the root object) in turn applying this recursively on all possible sub-objects.
(I assume by "empty" you mean the property is set and equal to null.)
I generated the following JSON:
{
"someString" : "example",
"obj1" : {
"opt1" : 1,
"opt2" : 1,
"opt3" : "aaa"
},
"obj2" : {
"opt1" : 55,
"opt2" : 55,
"opt3" : "bbb"
}
}
and there will be more of objects(obj1, obj2, obj3, obj4, ...) with same data type (opt1, opt2, opt3)
now I want to create schema for this but i don't know how to combine all this objects in schema.
EDIT:
I created schema:
root: {
"type" : "object",
"oneOf" : [
{
"properties" : {
"someString" : { "type" : "string" }
},
"patternProperties" : { "^.*$" : { "$ref" : "./schemas/myPatternProperties.json#" } },
"additionalProperties" : false }
}
]
}
and myPatternProperties.json looks:
{
"type" : "object",
"properties" : {
"opt1" : { "type" : "number" },
"opt2" : { "type" : "number" },
"opt3" : { "type" : "string" },
}
"required" : [ "opt1", "opt2", "opt3" ]
}
Is there anything wrong because, my generated JSON is still not recognized as this schema type.
As I understand your problem is to describe object with a lot of properties with the same type and some naming rules. To solve that you must specify patternProperties section
{
"patternProperties": {
"^(/[^/]+)+$": { "$ref": "http://some.site.somewhere/entry-schema#" }
}
that construction specify pattern to match for properties. Example how to use patternProperties Read more at specification
UPDATE
Actually full scheme must be something like that
{
"$schema": "http://json-schema.org/draft-06/schema#",
"type": "object",
"properties": {
"someString": {
"type": "string"
}
},
"patternProperties": {
"^obj([0-9]+)$": {
"$ref": "#/definitions/objEntity"
}
},
"additionalProperties": false,
"required": [ "someString" ],
"definitions": {
"objEntity": {
"type": "object",
"properties": {
"opt1": { "type": "number" },
"opt2": { "type": "number" },
"opt3": { "type": "string" }
},
"required": ["opt1", "opt2", "opt3"]
}
}
}
Of course you can split that scheme to more than 1 file, and change links to type definitions.
the main problem resides on validate a json against a schema that deals with arrays. So, if I put a different value seems to be still valid?
json schema:
{
"transactions" : {
"type" : "array",
"items" : {
"type" : "object",
"properties" : {
"type" : {
"type" : "string",
"enum" : ["BREAK"]
},
"required":["type"]
},
"required":["items"]
}
}
}
Input JSON:
{
"transactions":[
{
"type":"BREAKDDDDDdddddddddddddddddddddddddddJDJDJDJDJDJDJDJ"
}
]
}
result: No errors found. JSON validates against the schema.
This is wrong as we haven't defined an enum type like "BREAKDDDDD"
http://www.jsonschemavalidator.net/
Any thoughts on this?
Your JSON Schema is missing certain attributes. Look at the example provided here on how to start the schema http://json-schema.org/example1.html.
Update your schema to the below and try
{
"type": "object",
"properties": {
"transactions": {
"type": "array",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["BREAK"]
}
},
"required": ["type"]
}
}
}
}
To validate an enum value in a JSON schema, we can use the enum keyword. The enum keyword allows you to specify a list of allowed values for a property. For example, if you have an "enum": ["one", "two"] in your JSON schema, this will only allow the values "one", "two".
{
"type": "object",
"properties": {
"prop": {
"type": "string",
"enum": ["one", "two"]
}
}
}
Wondering if this is possible with schema draft 03. I've gotten dependencies working elsewhere, I think there is possibly just some creative use of them required in order to use them for specifying the required property of some field.
My current best attempt (which doesn't work) should give you some idea of what I'm after. I want a value required by default, and optional when another field has a particular value.
{
"description" : "An address...",
"type" : "object",
"properties" : {
"postcode": {
"type" : "string",
// postcode should be required by default
"required" : true,
// postcode shouldn't be required if the country is new zealand
"dependencies" : {
"country" : {
"enum" : ["NZ", "NZL", "NEW ZEALAND"]
},
"postcode" : {
"required" : false
}
}
},
"country": {
"type" : "string",
"enum" : [
// various country codes and names...
],
"default" : "AUS"
}
}
}
This is definitely possible with version 3 of the draft. Since you have a complete list of allowed countries, then you could do something like this:
{
"type": [
{
"title": "New Zealand (no postcode)",
"type": "object",
"properties": {
"country": {"enum": ["NZ", "NZL", "NEW ZEALAND"]}
}
},
{
"title": "Other countries (require postcode)",
"type": "object",
"properties": {
"country": {"enum": [<all the other countries>]},
"postcode": {"required": true}
}
}
],
"properties": {
"country": {
"type" : "string",
"default" : "AUS"
},
"postcode": {
"type" : "string"
}
}
}
So you actually define two sub-types for your schema, one for countries that require a postcode, and one for countries that do not.
EDIT - the v4 equivalent is extremely similar. Simply rename the top-level "type" array to "oneOf".
If anybody is looking for a solution for draft 4 you can use dependencies keyword together with a enum keyword:
{
"type": "object",
"properties": {
"play": {
"type": "boolean"
},
"play-options": {
"type": "string"
}
},
"dependencies": {
"play-options": {
"properties": {
"play": {
"enum": [true]
}
}
}
}
}
In this wayplay-options will always require play value to be true.
In the latest schema you can use the oneOf conditional to do this.
{
"description" : "An address...",
"type" : "object",
"properties" : {
"postcode": {
"type" : "string"
},
"country": {
"type" : "string",
"enum" : [
// various country codes and names...
],
"default" : "AUS"
}
},
"oneOf": [
{
"properties": {
"country": { "enum" : ["NZ", "NZL", "NEW ZEALAND"] }
}
},
{ "required": ["postcode"] }
]
}
The oneOf condition requires that one of the conditions in the array is true.
I just looked over the 03 version of the spec and I don't think what you are describing is possible. It's definitely not "Simple Dependency" and the description of "Schema Dependency" doesn't mention any way to consider the value of the property.
It sounds like what you need is "Conditional Schema Dependency".
There's some discussion of what's possible with Simple and Schema dependencies here:
http://groups.google.com/group/json-schema/msg/8145690ebb93963b
You might ask that group if there are plans to support conditional dependencies.