Validate using a specific definition with ajv - jsonschema

I have a JSON Schema file describing my API. It consists of some definitions as well as some vestigial parts from codegen that I'd like to ignore (the properties and required fields):
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"CreateBook": {
"properties": {
"title": {"type": "string"},
"author": {"type": "string"},
"numPages": {"type": "number"}
},
"required": ["title", "author"]
},
"CreateShelf": {
"properties": {
"books": {"type": "array", "items": {"type": "string"}}
},
"required": ["books"]
}
},
"properties": {
"/api/create-book": {
"properties": {"type": {"post": {"$ref": "#/definitions/CreateBook"}}},
"required": ["post"],
"type": "object"
},
"/api/create-shelf": {
"properties": {"type": {"post": {"$ref": "#/definitions/CreateShelf"}}},
"required": ["post"],
"type": "object"
}
},
"required": ["/api/create-book", "/api/create-shelf"],
"type": "object"
}
I'd like to validate requests according to the definitions. I'd like to completely ignore the properties and required fields, which describe the shape of the API itself, not the individual requests.
Given what I expect to be a CreateBook request and this JSON schema, how should I validate it?
Here's what I tried:
const ajv = new Ajv();
const validate = ajv.compile(jsonSchema);
const body = {
author: 'Roald Dahl',
numPages: 234,
// missing title
};
if (!validate(body, '#/definitions/CreateBook')) {
console.log(validate.errors);
}
This logs:
[
{
keyword: 'required',
dataPath: '#/definitions/CreateBook',
schemaPath: '#/required',
params: { missingProperty: '/api/create-book' },
message: "should have required property '/api/create-book'"
}
]
So it's ignoring the dataPath parameter ('#/definitions/CreateBook'). What's the right way to do this? Do I need to create a new schema for every request type?

If you use addSchema instead of compile to compile the schema, you can specify a fragment.
const ajv = new Ajv();
ajv.addSchema(jsonSchema);
const validate = ajv.getSchema("#/definitions/CreateBook");
const body = {
author: 'Roald Dahl',
numPages: 234,
// missing title
};
if (!validate(body)) {
console.log(validate.errors);
}

Related

Json schema conditional validation configuration - Unsupported keyword(s): ["const"]]

I want to set up the conditional validation in my schema. I saw an example here on SO.
I have a similar setup, where I would like to validate if the field public is set to string "public". If it is set to "public" then I want to make fields description, attachmentUrl and tags required. If the field is not set to "public" then this fields are not required.
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Update todo",
"type": "object",
"properties": {
"public": {
"type": "string"
},
"description": {
"type": "string",
"minLength": 3
},
"tags": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true,
"minItems": 1
},
"attachmentUrl": {
"type": "string"
}
},
"anyOf": [
{
"not": {
"properties": {
"public": { "const": "public" }
},
"required": ["public"]
}
},
{ "required": ["description", "tags", "attachmentUrl"] }
],
"additionalProperties": false
}
But, when I try to deploy it like that, I get the following error:
Invalid model specified: Validation Result: warnings : [], errors :
[Invalid model schema specified. Unsupported keyword(s): ["const"]]
The "const" keyword wasn't added until draft 06. You should upgrade to an implementation that supports at least that version.
https://json-schema.org/draft-06/json-schema-release-notes.html#additions-and-backwards-compatible-changes
Otherwise, you can use "enum" with a single value: "enum": ["public"]

Json Schema template for valiadtion

I am new to defining JSON schema and validating json against the schema.
Here is a sample json for which I want to define a json schema template for validation:
{
 "version": "1.0",
 "config": {
   "globalConfig": {
      “ClientNames”: [
        “client1”, “client2”, “client3”
       ]
    },
“ClientConfigs”: [
{
“ClientName”: “client1”,
“property1”: “some value”,
“property2”: “some value”
},
{
“ClientName”: “client2”,
“property1”: “some value”,
“property2”: “some value”
},
{
“ClientName”: “client3”,
“property1”: “some value”,
“property2”: “some value”
}
]
}
From what I understand “ClientConfigs” would be an array of object (let’s say ClientConfig) which will contain clientName, property1 and property2. Here is what I think schema would like:
{
"$schema": "http://json-schema.org/draft-01/schema#",
"title": "ClientConfig",
"type": "object",
"description": "Some configuration",
"properties": {
"version": {
"type": "string"
},
"config": {
"$ref": "#/definitions/config"
}
},
"definitions": {
"config": {
"type": "object",
"properties": {
"globalConfig": {
"type": "object",
"description": "Global config for all clients",
"properties": {
"ClientNames": {
"type": "array",
"minItems": 1,
"items": {
"type": "string"
}
}
}
},
"ClientConfigs": {
"type": "array",
"description": "List of configs for different clients",
"minItems": 1,
"items": {
"$ref": "#/definitions/ClientConfig"
}
}
}
},
"ClientConfig": {
"type": "object",
"properties": {
"ClientName": {
"type": "string"
},
"property1": {
"type": "string"
},
"property2": {
"type": "string"
}
}
}
}
}
I want to validate 2 things with jsonschema:
ClientName in every element of ClientConfigs array is one of the values from “ClientNames” i.e. individual ClientConfig in “ClientConfigs” array should only contain client names defined in property “ClientNames”.
Every clientName present in “ClientNames” should be defined as an element in “ClientConfigs” array. To be more precise, ClientConfig is defined for every clientName present in “ClientNames” property.
Here is an example which is NOT valid according to my requirements:
{
 "version": "1.0",
 "config": {
   "globalConfig": {
      “ClientNames”: [
        “client1”, “client2”, “client3”
       ]
    },
“ClientConfigs”: [
{
“ClientName”: “client4”,
“property1”: “some value”,
“property2”: “some value”
}
]
}
It is invalid because:
It doesn’t define ClientConfig for client1, client2 and client3.
It defines ClientConfig for client4 which is not present in “ClientNames”.
Is it possible to do such validation using json schema template? If yes, how to validate the same?
You cannot reference instance data in your JSON Schema. This is considered business logic and is out side of the scope for JSON Schema.

JSON schema - require a nested field

var schema = {
"id": "test.json#",
"definitions": {
"body": {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer"},
}
},
"request": {
"properties": {
"user": {
"$ref": "#/definitions/body"
}
}
}
},
"typeA": {
"$ref": "#/definitions/request"
},
"typeB": {
"allOf": [
{"$ref": "#/definitions/request"},
{"required": ["name"]} // Should require the name field in the user object.
]
}
}
var Ajv = require('ajv')
var ajv = Ajv({
schemas: [schema]
})
var data = {
user: {
"name": "tom",
"age": 1
}
}
var valid = ajv.validate({$ref: "test.json#/typeA"}, data)
console.log('VALID', valid) // Returns true.
var valid = ajv.validate({$ref: "test.json#/typeB"}, data)
console.log('VALID', valid) // Returns false.
I'm new to JSON schemas. I'd like to reuse the definitions-request-schema in typeA and typeB. typeB should require the name field in the user-object. How do I achieve this? I know why my solution above is not working. Required should be something like "user.name".
Thanks you for your help.

jsonSchema attribute conditionally required based on uri parameter

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.

Is it possible to inline JSON schemas into a JSON document? [duplicate]

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.