Running ajv-cli as part of my automated testing scripts to make sure my mock data is up to date.
./node_modules/.bin/ajv -s ./test-data/manifest.schema.json -d ./test-data/fleet.manifest.json
./test-data/fleet.manifest.json valid
But the data isn't valid.
manifest.schema.json:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"ManifestHistoryItem": {
"properties": {
"id": {
"default": [
"assetCatalog",
"Roster"
],
"items": {
"type": "string"
},
"type": "array"
},
"name": {
"default": "",
"type": "string"
}
},
"required": [
"id",
"name"
],
"type": "object"
}
}
}
fleet.manifest.json:
{
"namee": "Epic Space Battles"
}
(it's missing the required "id" property, and "name" is misspelled)
Schema is generated from "typescript-json-schema": "^0.54.0" from a typescript model and evaluated via "ajv-cli": "^5.0.0".
Your schema declares definitions, but it doesn't reference them anywhere. You need to add a "$ref": "#/definitions/ManifestHistoryItem" at the root.
{
"definitions": {
"ManifestHistoryItem": { ... }
},
"$ref": "#/definitions/ManifestHistoryItem"
}
Either that or you can just get rid of the definitions wrapper altogether and just have the { ... } part from above.
Effectively what's happening is you've defined an empty schema, which applies no constraints, meaning all instances (data) pass.
Related
I have a jsonschema definition definition-1.json like below. I have a property called inputs of type array which refers anyOf definitions like
"inputs": {
"type": "array",
"items": {
"anyOf": [
{
"$ref": "#/definitions/stringParameter"
},
{
"$ref": "#/definitions/numberParameter"
},
{
"$ref": "#/definitions/booleanParameter"
},
{
"$ref": "#/definitions/jsonParameter"
},
{
"$ref": "#/definitions/fileParameter"
}
]
}
}
Then the parameter definitions defined like below in the same jsonschema definition-1.json
"definitions": {
"stringParameter": {
"type": "object",
"required": [
"name",
"description",
"datatype"
]
}
...
}
Now I want to reference this parameter definitions in my other schema dummy-1.json like below
{
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "dummy-1.json",
"allOf": [
{
"type": "object",
"required": [
"definition_data"
],
"properties": {
"definition_data": {
"type": "object",
"required": [
"inputs"
],
"properties": {
"type": "array",
"inputs": {
"items": {
"allOf": [
{
"prop1": {
"$ref": "#/definitions/stringParameter"
}
},
{
"prop2": {
"$ref": "#/definitions/numberParameter"
}
}
]
}
}
}
}
}
},
{
"type": "object",
"properties": {
"definition_data": {
"$ref": "definition-1.json"
}
}
}
]
}
This doesn't looks like working. prop1 will validate successfully for any properties part of other parameter definition too , even though in dummy-1.json I explicitly referred #/definitions/stringParameter .
I can understand inputs in definition-1.json accepts anyOf all parameter definitions. But want to know how we can achieve 1:1 parameter definition mapping for dummy-1.json .
If my understanding is correct, you want to refer from dummy-1.json to subschemas defined in definitions-1.json. To do that, you have to specify the URI of definition-1.json and append the pointer of the subschema in it, like:
dummy-1.json:
{
"$ref": "uri-of-definitions-1.json#/definitions/stringParameter"
}
Note that the URI to be set is quite specific to the json schema library you are using. Usually an absolute https:// URL works, but it isn't always convenient to work with. Maybe a relative path can work for you as well (like ./definition-1.json#/definitions/stringParameter)
Suppose I have two schema being used to validate a json file.
testSchema.json
{
"$schema": "http://json-schema.org/draft-07/schema",
"type": "object",
"additionalProperties": false,
"properties": {
"$schema": { "type": "string" },
"sample": { "type": "number" }
},
"anyOf": [
{ "$ref": "./testSchema2.json" },
{}
]
}
testSchema2.json
{
"$schema": "http://json-schema.org/draft-04/schema",
"type": "object",
"properties": {
"test": { "type": "string" },
"test2": { "type": "number" }
}
}
test.json
{
"$schema": "../testSchema.json",
"sample": 0,
"test": "some text" //this line throws error "Property is not allowed"
}
I'd like for the file to be validated against the included schema's properties and any schema that is referenced's properties. Am I missing something?
Edit: I want to exclude any objects that are not explicitly defined in any of my included/referenced schema.
From JSON Schema draft 2019-09 (after draft-07), this is possible by using the unevaluatedProperties keyword.
additionalProperties cannot "see through" applicator keywords such as "anyOf" and "$ref", and only works based on the properties in the same schema object.
This is not possible with draft-07 or previous.
I defined in the schema a validType, where every attribute should have text and annotation .
I want to add additional constraints to refine the text of course must follow "pattern":"[a-z]{2}[0-9]{2}". Is there any way I can apply the constraint directly without copy&paste the content of the validType?
Schema:
{
"type": "object",
"definition": {
"validType": {
"description": "a self-defined type, can be complicated",
"type": "object",
"properties": {
"text": {
"type": "string"
},
"annotation": {
"type": "string"
}
}
},
"properties": {
"name": {
"$ref": "#/definitions/validType"
},
"course": {
"$ref": "#/definitions/validType"
}
}
}
}
Data:
{"name":{
"text":"example1",
"annotation":"example1Notes"},
"course":{
"text":"example2",
"annotation":"example2Notes"}}
The expected schema for course should work as this:
{"course": {
"type": "object",
"properties": {
"text": {
"type": "string",
"pattern":"[a-z]{2}[0-9]{2}"
},
"annotation": {
"type": "string"
}
}
}}
But instead of repeating the big block of validType, I am expecting something similar to the format below:
{"course": {
"$ref": "#/definitions/validType"
"text":{"pattern":"[a-z][0-9]"}
}}
Yup! You can add constraints but you cannot modify the constraints you reference.
To add constraints, you need to understand that $ref for draft-07 and previous is the only allowed key in a subschema when it exsits. Other keys are ignored if it exists.
As such, you need to create two subschemas, one of which has your reference, and the other your additional constraint.
You then wrap these two subschemas in an allOf.
Here's how that would look...
{
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"course": {
"allOf": [
{
"$ref": "#/definitions/validType"
},
{
"properties": {
"text": {
"pattern": "[a-z][0-9]"
}
}
}
]
}
}
}
Have a play using https://jsonschema.dev
Consider an API with an endpoint in which you pass a parameter foo as part of the URL-path, and pass some parameters as json in the body of a POST request.
This uri parameter foo must have one of the values fooBar and fooBaz. Otherwise the request gest a 404.
If the value of foo is fooBar then the body attribute bar is required.
If the value of foo is fooBaz then the body attribute baz is required.
How do I specify a jsonSchema for such an endpoint?
An example for such a request would be:
POST /path/to/endpoint/fooBar HTTP/1.1
Accept: application/json
Content-Type: application/json
Content-Length: 20
{"bar":"something"}
So far I came up with the following based on this and that but I have no idea if this is correct.
{
"$schema": "http://json-schema.org/draft-03/schema#",
"id": "http://json-schema.org/draft-03/schema#",
"definitions": {
"foo": { "enum": ["fooBar", "fooBaz"] }
},
"type": "object",
"properties": {
"foo": { "$ref": "#/definitions/foo" },
"bar": { "type": "string" },
"baz": { "type": "string" }
},
"links": [{
"rel": "self",
"href": "/path/to/endpoint/{foo}",
"hrefSchema": {
"properties": {
"foo": {"$ref": "#/definitions/foo"}
}
}
}],
"anyOf": [
{
"properties": {
"foo": { "enum": ["fooBar"] }
},
"required": ["bar"]
},
{
"properties": {
"foo": { "enum": ["fooBaz"] }
},
"required": ["baz"]
},
]
}
JSON Schema validation is not aware of the URI that the data being validated came from (if any). You can use JSON Hyper-Schema to tell your users how to send that data in the way you expect, but you will still need to do an additional check on the server-side to validate that the request was sent properly.
Before I get into the solution, I want to point out a couple things. Your $schema is set to "draft-03". If you really need "draft-03", this solution won't work. anyOf, allOf and the array form of required were added in "draft-04". hrefSchema was added in "draft-06". Since "draft-06" is brand new and is not well supported yet and you don't need hrefSchema anyway, I'm going to assume "draft-04" ("draft-05" was effectively skipped).
The next thing to mention is that you are using id wrong. It should be the identifier for your schema. You usually don't need this, but if you do, it should be the full URI identifying your schema. That is how my solution uses it.
Last thing before I get into the solution. If you are using the link keyword, you are using JSON Hyper-Schema and your $schema should reflect that. It should have "hyper-schema" instead of "schema" at the end of the URI.
Now the solution. I broke it into two parts, the schema that you put through the validator and the hyper-schema that tells users how to make the request. You've got the first one right. I only fixed the $schema and id.
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "http://example.com/schema/my-foo-schema",
"type": "object",
"properties": {
"foo": { "enum": ["fooBar", "fooBaz"] },
"bar": { "type": "string" },
"baz": { "type": "string" }
},
"anyOf": [
{
"properties": {
"foo": { "enum": ["fooBar"] }
},
"required": ["bar"]
},
{
"properties": {
"foo": { "enum": ["fooBaz"] }
},
"required": ["baz"]
}
]
}
Next is the hyper-schema. You can't reference anything external (href, instance data) in a request schema, but you can write your schema so that it matches your href. The duplication is unfortunate, but that's the way you have to do it.
{
"$schema": "http://json-schema.org/draft-04/hyper-schema#",
"links": [
{
"rel": "http://example.com/rel/my-foo-relation",
"href": "/path/to/endpoint/fooBar",
"method": "POST",
"schema": {
"allOf": [{ "$ref": "http://example.com/schema/my-foo-schema" }],
"properties": {
"foo": { "enum": ["fooBar"] }
}
}
},
{
"rel": "http://example.com/rel/my-foo-relation",
"href": "/path/to/endpoint/fooBaz",
"method": "POST",
"schema": {
"allOf": [{ "$ref": "http://example.com/schema/my-foo-schema" }],
"properties": {
"foo": { "enum": ["fooBaz"] }
}
}
},
{
"rel": "self",
"href": "/path/to/endpoint"
}
]
}
Usually when I come across something that is difficult to model with hyper-schema, it means that I am doing something overly complicated with my API and I need to refactor. I suggest taking a few minutes to think about alternative designs where this switching on an enum isn't necessary.
For example a schema for a file system, directory contains a list of files. The schema consists of the specification of file, next a sub type "image" and another one "text".
At the bottom there is the main directory schema. Directory has a property content which is an array of items that should be sub types of file.
Basically what I am looking for is a way to tell the validator to look up the value of a "$ref" from a property in the json object being validated.
Example json:
{
"name":"A directory",
"content":[
{
"fileType":"http://x.y.z/fs-schema.json#definitions/image",
"name":"an-image.png",
"width":1024,
"height":800
}
{
"fileType":"http://x.y.z/fs-schema.json#definitions/text",
"name":"readme.txt",
"lineCount":101
}
{
"fileType":"http://x.y.z/extended-fs-schema-video.json",
"name":"demo.mp4",
"hd":true
}
]
}
The "pseudo" Schema note that "image" and "text" definitions are included in the same schema but they might be defined elsewhere
{
"id": "http://x.y.z/fs-schema.json",
"definitions": {
"file": {
"type": "object",
"properties": {
"name": { "type": "string" },
"fileType": {
"type": "string",
"format": "uri"
}
}
},
"image": {
"allOf": [
{ "$ref": "#definitions/file" },
{
"properties": {
"width": { "type": "integer" },
"height": { "type": "integer"}
}
}
]
},
"text": {
"allOf": [
{ "$ref": "#definitions/file" },
{ "properties": { "lineCount": { "type": "integer"}}}
]
}
},
"type": "object",
"properties": {
"name": { "type": "string"},
"content": {
"type": "array",
"items": {
"allOf": [
{ "$ref": "#definitions/file" },
{ *"$refFromProperty"*: "fileType" } // the magic thing
]
}
}
}
}
The validation parts of JSON Schema alone cannot do this - it represents a fixed structure. What you want requires resolving/referencing schemas at validation-time.
However, you can express this using JSON Hyper-Schema, and a rel="describedby" link:
{
"title": "Directory entry",
"type": "object",
"properties": {
"fileType": {"type": "string", "format": "uri"}
},
"links": [{
"rel": "describedby",
"href": "{+fileType}"
}]
}
So here, it takes the value from "fileType" and uses it to calculate a link with relation "describedby" - which means "the schema at this location also describes the current data".
The problem is that most validators do not take any notice of any links (including "describedby" ones). You need to find a "hyper-validator" that does.
UPDATE: the tv4 library has added this as a feature
I think cloudfeet answer is a valid solution. You could also use the same approach described here.
You would have a file object type which could be "anyOf" all the subtypes you want to define. You would use an enum in order to be able to reference and validate against each of the subtypes.
If the sub-types schemas are in the same Json-Schema file you don't need to reference the uri explicitly with the "$ref". A correct draft4 validator will find the enum value and will try to validate against that "subschema" in the Json-Schema tree.
In draft5 (in progress) a "switch" statement has been proposed, which will allow to express alternatives in a more explicit way.