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
undefined
means the property does not exist. To set a property's value to "no value", set it toundefined
.null
on 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
, anduniqueItems
number
andinteger
:multipleOf
,maximum
, andminimum
object
:maxProperties
,minProperties
,required
,properties
,additionalProperties
,patternProperties
, anddependencies
string
: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#validateOnSet
to 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: true
to 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 over 7 years ago
Continue with part 7 of the Tutorial or explore other documentation.