6. Schemas & Validation
Defining and validating the structure of your Records
This tutorial needs review
Introduction
This pages assumes you've read part 5 of the tutorial, Saving data.
A Schema represents how your data is organized. The following JSData functionalities require that you define a Schema:
- Change Detection
- Validation
- Strict JSONification of Records
JSData Schemas following an extended version of http://json-schema.org/. Let's get started.
Defining a schema
Schemas are defined on a per-Mapper basis. One Schema per Mapper. A Mapper represents the CRUD operations that can be performed on a Resource, and its Schema represents the form that the Records of the Mapper will take. There are several ways to define a Schema:
import { Schema, Container } from 'js-data';
const personSchema = new Schema({
$schema: 'http://json-schema.org/draft-04/schema#', // optional
title: 'Person', // optional
description: 'Schema for Person Records.', // optional
type: 'object', // required
properties: {
name: { type: 'string' }
}
});
const store = new Container();
store.defineMapper('person', {
schema: personSchema
});
import { Container } from 'js-data';
const store = new Container();
store.defineMapper('person', {
// here we define the schema inline, but you can also
// provide a reference to a standalone schema
schema: {
$schema: 'http://json-schema.org/draft-04/schema#', // optional
title: 'Person', // optional
description: 'Schema for Person Records.', // optional
type: 'object', // required
properties: {
name: { type: 'string' }
}
}
});
import { Mapper } from 'js-data';
const personService = new Mapper({
name: 'person',
// here we define the schema inline, but you can also
// provide a reference to a standalone schema
schema: {
$schema: 'http://json-schema.org/draft-04/schema#', // optional
title: 'Person', // optional
description: 'Schema for Person Records.', // optional
type: 'object', // required
properties: {
name: { type: 'string' }
}
}
});
Schema Types
JSON Schema defines seven primitive types for JSON values:
array- A JSON array.boolean- A JSON boolean.integer- A JSON number without a fraction or exponent part.number- Any JSON number. Number includes integer.null- The JSON null value.object- A JSON object.string- A JSON string.
Here's a more complex example:
import { Schema } from 'js-data';
const productSchema = new Schema({
$schema: 'http://json-schema.org/draft-04/schema#',
title: 'Product',
description: 'A product from Acme\'s catalog',
type: 'object',
properties: {
id: {
description: 'The unique identifier for a product',
type: 'number'
},
name: { type: 'string' },
price: { type: 'number' },
tags: {
type: 'array',
items: { type: 'string' }
},
dimensions: {
type: 'object',
properties: {
length: { type: 'number' },
width: { type: 'number' },
height: { type: 'number' }
},
required: ['length', 'width', 'height']
},
warehouseLocation: {
description: 'Coordinates of the warehouse with the product',
type: 'object',
properties: {
latitude: { type: 'number' },
longitude: { type: 'number' }
}
}
},
required: ['id', 'name', 'price']
});
Tip
To JSData, a value of
undefinedmeans the property does not exist. To set a property's value to "no value", set it toundefined.nullon the hand means the property exists and has the value ofnull. If a property can take a value ofnull, then make a note of that in the schema:name: { type: ['string', 'null'] }
Type Keywords
Assigning types to a property enables certain validation keywords to be used for the property. For example, if a name property has a type of string, then the maxLength keyword can be used to invalidate the property if its value's length exceeds the specified limit. The maxLength keyword doesn't make any sense in the context of a number type, and therefore will have no effect if a property's value is of type number.
Here are the keywords available to each type:
array:items,maxItems,minItems, anduniqueItemsnumberandinteger:multipleOf,maximum, andminimumobject:maxProperties,minProperties,required,properties,additionalProperties,patternProperties, anddependenciesstring:maxLength,minLength, andpattern
The following keywords are available for all types: enum, type, allOf, anyOf, oneOf, and not.
Tip
Read more about the available Type Validation Keywords at http://json-schema.org/latest/json-schema-validation.html
Let's add some keywords to the Product Schema example:
import { Schema } from 'js-data';
const productSchema = new Schema({
$schema: 'http://json-schema.org/draft-04/schema#',
title: 'Product',
description: 'A product from Acme\'s catalog',
type: 'object',
properties: {
id: {
description: 'The unique identifier for a product',
type: 'number',
minimum: 1
},
name: {
type: 'string',
maxLength: 255
},
price: {
type: 'number',
minimum: 0,
exclusiveMinimum: true
},
tags: {
type: 'array',
items: { type: 'string' },
minItems: 1,
uniqueItems: true
},
dimensions: {
type: 'object',
properties: {
length: { type: 'number' },
width: { type: 'number' },
height: { type: 'number' }
},
required: ['length', 'width', 'height']
},
warehouseLocation: {
description: 'Coordinates of the warehouse with the product',
type: 'object',
properties: {
latitude: { type: 'number' },
longitude: { type: 'number' }
}
}
},
required: ['name', 'price']
});
Validating records
With a Schema you can validate any value against the Schema:
import { Schema } from 'js-data';
const personSchema = new Schema({
type: 'object', // required
properties: {
name: { type: 'string' }
}
});
// These are invalid!
personSchema.validate('foo'); // [{...}]
personSchema.validate(1234); // [{...}]
personSchema.validate(['bar']); // [{...}]
// Success!
personSchema.validate({ name: 'John' }); // undefined
import { Schema, Container } from 'js-data';
const personSchema = new Schema({
type: 'object', // required
properties: {
name: { type: 'string' }
}
});
const store = new Container();
store.defineMapper('person', {
schema: personSchema
});
// These are invalid!
store.validate('person', 'foo'); // [{...}]
store.validate('person', 1234); // [{...}]
store.validate('person', ['bar']); // [{...}]
// Success!
store.validate('person', { name: 'John' }); // undefined
import { Schema, Mapper } from 'js-data';
const personSchema = new Schema({
type: 'object', // required
properties: {
name: { type: 'string' }
}
});
const personService = new Mapper({
name: 'person',
schema: personSchema
});
// These are invalid!
personService.validate('foo'); // [{...}]
personService.validate(1234); // [{...}]
personService.validate(['bar']); // [{...}]
// Success!
personService.validate({ name: 'John' }); // undefined
// Skip validation on instantiation
const person = store.createRecord('person',{
name: 'John'
}, {
noValidate: true
});
console.log(person.validate()); // [{...}]
person.name = 'John';
console.log(person.validate()); // undefined
const person = store.createRecord('person',{
name: 'John'
});
person.name = 123; // Throws an error
Records validate themselves upon instantiation:
// These are invalid!
try {
store.createRecord('person', { name: 1234 });
} catch (err) {
console.log(err.message); // "validation failed"
console.log(err.errors); // [{...}]
}
// Success!
store.createRecord('person', { name: 'John' });
import { Container, Record, Schema } from 'js-data';
const personSchema = new Schema({
type: 'object', // required
properties: {
name: { type: 'string' }
}
});
const store = new Container();
class PersonRecord extends Record {}
store.defineMapper('person', {
schema: personSchema,
recordClass: PersonRecord
});
let person;
// These are invalid!
try {
person = new PersonRecord({ name: 1234 });
} catch (err) {
console.log(err.message); // "validation failed"
console.log(err.errors); // [{...}]
}
// Success!
person = new PersonRecord({ name: 'John' });
Records also validate properties on assignment:
// Success!
const person = store.createRecord('person', { name: 'John' });
try {
person.name = 1234;
} catch (err) {
console.log(err.message); // "validation failed"
console.log(err.errors); // [{...}]
}
To disable property validation on assignment, set Mapper#validateOnSet to false, or pass validateOnSet: false to createRecord() or add().
Tip
You can disable validation on assignment by setting
Mapper#validateOnSetto false.
Records can also tell you whether they are currently in a valid state:
// Skip validation on instantiation
const person = store.createRecord('person',{
name: 'John'
}, {
noValidate: true
});
console.log(person.isValid()); // false
person.name = 'John';
console.log(person.isValid()); // true
Configuring validation hooks
JSData automatically calls Schema#validate for you when you try to create or update records via an adapter, for example:
store.create('person', {
name: 1234
}).catch((err) => {
console.log(err.message); // "validation failed"
console.log(err.errors); // [{...}]
});
store.createMany('person', [{
name: 1234
}, {
name: 'Sally'
}).catch((err) => {
console.log(err.message); // "validation failed"
console.log(err.errors); // [{...}]
});
store.update('person', 1234567890, {
name: 1234
}).catch((err) => {
console.log(err.message); // "validation failed"
console.log(err.errors); // [{...}]
});
store.updateAll('comment', {
status: 'flagged'
}, {
user_id: 123457909
}).catch((err) => {
console.log(err.message); // "validation failed"
console.log(err.errors); // [{...}]
});
store.updateMany('person', [{
id: 1234567890,
name: 1234
}, {
id: 4736583920,
name: 'Sally'
}).catch((err) => {
console.log(err.message); // "validation failed"
console.log(err.errors); // [{...}]
});
Tip
You can skip validation by passing
noValidate: trueto the method call.
Custom validation
Extending the Schema class
TODO
Validation Keywords:
TODO
Types:
TODO
Type Group Validators:
TODO
Extending Mapper#validate
TODO
class MyMapper extends Mapper {
validate (...args) {
// do some custom validation
if (/* some condition */) {
return [{ expected: 'some expectation', actual: 'actual value' }];
}
// resume default behavior
return super.validate(...args);
}
See an issue with this tutorial?
You can open an issue or better yet, suggest edits right on this page.
Updated about 9 years ago
Continue with part 7 of the Tutorial or explore other documentation.
