@jalik/schema v4.2.0
@jalik/schema
Features
- Define object structure and constraints
- Several constraints available
- type, required, length, min, max, minWords, maxWords, allowed, denied, pattern, format
- Custom check using a function
- Full object validation
- Object field validation
- Specific errors are thrown for each field
- Similar to the JSON Schema specification
- TypeScript declarations ♥
Sandbox
Play with the lib here: https://codesandbox.io/s/jalik-schema-demo-5o4fnk?file=/src/index.js
Installing
npm i -P @jalik/schemayarn add @jalik/schemaCreating a schema
Let's start with the schema of a person, very simple.
import { Schema } from '@jalik/schema';
const PersonSchema = new Schema({
age: {
type: 'integer',
required: true,
min: 1,
},
gender: {
type: 'string',
required: true,
allowed: ['male', 'female'],
},
hobbies: {
type: 'array',
allowed: ['coding', 'playing', 'sleeping'],
},
name: {
type: 'string',
required: true,
maxLength: 50,
},
});
export default PersonSchema;Extending a schema (inheritance)
To create a new schema based on another, we can extend the base schema.
import PersonSchema from './PersonSchema';
const ParentSchema = PersonSchema.extend({
children: {
type: 'array',
items: { type: PersonSchema }
},
married: {
type: 'boolean',
required: true,
},
spouse: {
type: PersonSchema
}
});
export default ParentSchema;Cloning a schema
import { Schema } from '@jalik/schema';
const ProductSchema = new Schema({
name: {
type: 'string',
required: true
},
price: {
type: 'number',
required: true,
min: 0,
}
});
export default ProductSchema;
export const ASchema = ProductSchema.clone();
export const BSchema = ProductSchema.clone();
export const CSchema = ProductSchema.clone();Validating objects using a schema
You want to validate an object with a schema, for sure! This is why you're here.
import { Schema } from '@jalik/schema';
const PhoneSchema = new Schema({
code: {
type: 'string',
required: true,
},
number: {
type: 'string',
required: true,
},
});
const UserSchema = new Schema({
age: {
type: 'integer',
required: true,
min: 18,
},
gender: {
type: 'string',
required: true,
allowed: ['male', 'female'],
},
name: {
type: 'string',
required: true,
maxLength: 50,
},
phone: {
type: PhoneSchema,
},
});
// A valid object
const validUser = {
age: 33,
gender: 'male',
name: 'me',
};
// An invalid object
const invalidUser = {
age: 16,
gender: null,
phone: { code: 777, number: 10101001 },
};
try {
// This will not throw an error.
UserSchema.validate(validUser);
// This will throw a ValidationError.
UserSchema.validate(invalidUser);
} catch (error) {
if (error instanceof ValidationError) {
console.log(error.errors);
}
}
// It's also possible to check if a schema is valid without throwing error
if (UserSchema.isValid(invalidUser)) {
console.error('user is not valid');
}The ValidationError object looks like this:
{
"reason": "object-invalid",
"message": "Object is not valid",
"errors": {
"age": "\"age\" must be greater than or equal to 16",
"gender": "\"gender\" cannot be null",
"name": "\"name\" is required",
"phone.code": "\"code\" is not of type \"string\"",
"phone.number": "\"number\" is not of type \"string\""
}
}Note that you can get errors without having to "try/catch", by using the getErrors() method.
If there are no errors, it will return null;
// this example would return errors because phone.code and phone.number are not of type "string"
const errors = UserSchema.getErrors({
age: 16,
gender: null,
phone: { code: 777, number: 10101001 }
});Handling errors
If the schema fails to validate an object, it will throw a specific error (ex: FieldLengthError
, FieldTypeError, FieldRequiredError...), which is helpful to handle errors display.
For example, a FieldRequiredError looks like this:
{
"field": "Phone Number",
"message": "The field is required",
"path": "phones[0].number",
"reason": "field-required"
}If you need to detect the type of the error, it is recommended to check the reason attribute of
the error against a constant, instead of comparing class instance.
Here is the list of all errors types constants:
import {
ERROR_FIELD_ALLOWED,
ERROR_FIELD_DENIED,
ERROR_FIELD_FORMAT,
ERROR_FIELD_INVALID,
ERROR_FIELD_LENGTH,
ERROR_FIELD_MAX,
ERROR_FIELD_MAX_LENGTH,
ERROR_FIELD_MAX_WORDS,
ERROR_FIELD_MIN,
ERROR_FIELD_MIN_LENGTH,
ERROR_FIELD_MIN_WORDS,
ERROR_FIELD_PATTERN,
ERROR_FIELD_REQUIRED,
ERROR_FIELD_TYPE,
ERROR_FIELD_UNKNOWN,
} from '@jalik/schema';All errors are also available as an array:
import { errors } from '@jalik/schema';Translating errors
The first thing to do is to load the locales you need with setLocale(locale, object).
This can be done when your app starts or right before displaying an error.
Errors are in English by default, you can customize them or use them as this.
import { default as en } from '@jalik/schema/dist/locales/en';
import { default as fr } from '@jalik/schema/dist/locales/fr';
import { ERROR_FIELD_INVALID } from '@jalik/schema';
// Load default error messages in english.
setLocaleMessages('en', en);
// Or customize error messages.
setLocaleMessages('en', {
...en,
[ERROR_FIELD_INVALID]: 'This field is not valid :('
});
// Load other locales if needed.
setLocaleMessages('fr', fr);Then somewhere in your application, when you want to display the error, use
the getErrorMessage(error, locale) function.
import {
getErrorMessage,
Schema
} from '@jalik/schema';
const locale = 'fr';
// Prepare a schema.
const schema = new Schema({ age: { type: 'number' } });
// Check errors.
const errors = schema.getErrors({ age: '42' });
// Display the translated error messages.
Object.entries(errors).forEach(([path, error]) => {
const message = getErrorMessage(error, locale);
console.log(message);
});Contributions to translations are welcome, just create a pull request with the new locale files.
Checking field's type
Use type to check the type of the field value. It can be a basic type (array, boolean, number,
object, string), or an advanced type like an instance of Schema or an object constructor
like Date.
- Accepts
"array","boolean","integer","number","object","string",Date, or aSchema - Throws
FieldTypeError
import { Schema } from '@jalik/schema';
export const ExampleSchema = new Schema({
// The field must be an array of any values.
array: { type: 'array' },
// The field must be a boolean.
boolean: { type: 'boolean' },
// The field must be an integer.
integer: { type: 'integer' },
// The field must be a number (integer or float).
number: { type: 'number' },
// The field must be an object.
object: { type: 'object' },
// The field must be a string.
string: { type: 'string' },
// The field must matches UserSchema.
example: { type: ExampleSchema },
// The field must be an array of objects matching UserSchema.
examples: { type: 'array', items: { type: ExampleSchema } },
// The field must be an array of arrays.
arrayArray: { type: 'array', items: { type: 'array' } },
// The field must be an array of booleans.
booleanArray: { type: 'array', items: { type: 'boolean' } },
// The field must be an array of integers.
integerArray: { type: 'array', items: { type: 'integer' } },
// The field must be an array of numbers.
numberArray: { type: 'array', items: { type: 'number' } },
// The field must be an array of objects.
objectArray: { type: 'array', items: { type: 'object' } },
// The field must be an array of strings.
stringArray: { type: 'array', items: { type: 'string' } },
});Checking required values
Use required to check if a field is null or undefined.
Important: Since v4.0.0, a FieldRequired error will be thrown for null values, which was not the
case in previous versions (only with undefined)
- Accepts
BooleanorFunction - Throws
FieldRequiredError
import { Schema } from '@jalik/schema';
export const ExampleSchema = new Schema({
// The field is optional (this is the default).
optional: {
required: false
},
// The field must be defined.
required: {
required: true
},
});Checking maximum and minimum values
Use max and min to check if a field value is below or above a limit.
- Accepts
NumberorFunction - Throws
FieldMaxError,FieldMinError
import { Schema } from '@jalik/schema';
export const ExampleSchema = new Schema({
// The date must be between the previous hour and the next hour.
date: {
type: Date,
min: () => new Date(Date.now() - 3600 * 1000),
max: () => new Date(Date.now() + 3600 * 1000)
},
// The number must be negative.
negativeNumber: {
type: 'number',
max: -1
},
// The number must be positive.
positiveNumber: {
type: 'number',
min: 0
}
});Checking multiple of a number
Use multipleOf to accept only value that is a multiple of a number.
import { Schema } from '@jalik/schema'
const schema = new Schema({
channels: {
type: 'number',
multipleOf: 2
}
})
// This is valid
schema.validate({
channels: 8
})Checking length
Use maxLength and minLength to check the length of a field value. It works on any object with
a length attribute (String, Array...), so if you have objects like MyList.length, it will
work too.
- Accepts
NumberorFunction - Throws
FieldMaxLengthError,FieldMinLengthError
import { Schema } from '@jalik/schema';
export const ExampleSchema = new Schema({
// The array must have exactly two values.
arrayLength: {
type: 'array',
length: 2
},
// The string must have exactly ten characters.
fixedLength: {
type: 'string',
length: 10
},
// The string must have at least three characters.
minLength: {
type: 'string',
minLength: 3
},
// The array must have ten values or less.
maxLength: {
type: 'string',
maxLength: 10
},
// The string must have between five and ten characters (inclusive).
minMaxLength: {
type: 'string',
minLength: 5,
maxLength: 10
},
// It also works with objects having a length attribute.
objectWithLength: {
type: 'object',
minLength: 1,
maxLength: 1
}
});Checking words count
Use maxWords and minWords to limit words count in a string.
- Accepts
NumberorFunction - Throws
FieldMaxWordsError,FieldMinWordsError
import { Schema } from '@jalik/schema';
export const ExampleSchema = new Schema({
// The summary must not have more than 50 words.
summary: {
type: 'string',
maxWords: 50
},
// The description must have at least ten words.
description: {
type: 'string',
minWords: 10
},
});Checking minimum/maximum items
Use minItems and maxItems to limit the number of items in an array.
import { Schema } from '@jalik/schema'
const schema = new Schema({
emails: {
type: 'array',
minItems: 1,
maxItems: 2,
items: {
type: 'string'
}
}
})
// This is valid
schema.validate({
emails: [
'primary@mail.com',
'secondary@mail.com',
]
})Checking uniqueness of items
Use uniqueItems to force having unique items in an array.
import { Schema } from '@jalik/schema'
const schema = new Schema({
emails: {
type: 'array',
uniqueItems: true,
items: {
type: 'string'
}
}
})
// This is valid
schema.validate({
emails: [
'primary@mail.com',
'secondary@mail.com',
]
})Checking allowed values
Use allowed to check if a field value is allowed.
- Accepts
BooleanorFunction - Throws
FieldAllowedError
import { Schema } from '@jalik/schema';
export const ExampleSchema = new Schema({
// The string must be 'yes' or 'no'.
answer: {
type: 'string',
allowed: ['yes', 'no']
},
// The array must contain only 0 and 1 as numbers.
binaryNumbers: {
type: 'array',
items: { type: 'number' },
allowed: [0, 1]
},
// The array must contain only hot colors.
hotColors: {
type: 'array',
items: { type: 'string' },
allowed: ['red', 'yellow', 'orange']
},
});Checking denied values
Use denied to check if a field value is denied.
- Accepts
BooleanorFunction - Throws
FieldDeniedError
import { Schema } from '@jalik/schema';
export const ExampleSchema = new Schema({
message: {
type: 'string',
denied: ['duck', 'six', 'slot']
},
});Checking using a pattern (regular expression)
Use pattern to check if a field value matches a regular expression.
- Accepts
RegExp,StringorFunction - Throws
FieldPatternError
import { Schema } from '@jalik/schema';
export const ExampleSchema = new Schema({
time: {
type: 'string',
// The time must be like 'HH:mm'
pattern: '^\\d{1,2}:\\d{1,2}$'
},
password: {
type: 'string',
// The password must contain alphanumeric and special characters
pattern: /^[a-zA-Z0-9_&#@$*%?!]+$/
}
});Checking a format (based on regular expression)
Use format to check if a field value matches a specific known format.
- Accepts
StringorFunction - Throws
FieldFormatError
import { Schema } from '@jalik/schema';
export const ExampleSchema = new Schema({
date: {
type: 'string',
format: 'date'
},
datetime: {
type: 'string',
format: 'date-time'
},
email: {
type: 'string',
format: 'email'
},
hostname: {
type: 'string',
format: 'hostname'
},
ipv4: {
type: 'string',
format: 'ipv4'
},
ipv6: {
type: 'string',
format: 'ipv6'
},
time: {
type: 'string',
format: 'time'
},
uri: {
type: 'string',
format: 'uri'
},
});Checking using a function
Use check to apply custom checks that are not possible with the schema.
- Accepts
Function - Throws
FieldError
import { Schema } from '@jalik/schema';
export const ExampleSchema = new Schema({
evenNumber: {
type: 'number',
// The number must be even.
check: (value) => value % 2 === 0
}
});Setting field's label
Use label to set field's label, which will be used in errors, note that if the label is not set,
the field's name will be used instead.
- Accepts
StringorFunction
import { Schema } from '@jalik/schema';
export const ExampleSchema = new Schema({
birthday: {
type: Date,
label: 'Date of Birth'
},
});Parsing values
Use parse to parse string values before validation.
- Accepts
Function
import { Schema } from '@jalik/schema';
import DateTime from 'luxon';
export const ExampleSchema = new Schema({
birthday: {
type: 'string',
parse: (value) => DateTime.fromISO(value).toJSDate()
},
});Preparing values
Use prepare to perform some operations before validation.
- Accepts
Function
import { Schema } from '@jalik/schema';
export const ExampleSchema = new Schema({
// Execute the prepare function on field value
// before clean and check execution.
// It can be useful in some case
// where clean cannot be used to do what you want.
numbers: {
type: 'array',
items: { type: 'number' },
prepare: (numbers) => numbers.sort()
},
});
const result = ExampleSchema.prepare({ numbers: [5, 9, 0, 3, 2, 7] })So result will be:
{
"numbers": [
0,
2,
3,
5,
7,
9
]
}Cleaning values
Use clean to perform some cleaning on a value.
- Accepts
Function
import { Schema } from '@jalik/schema';
export const ExampleSchema = new Schema({
// Every items in the list will be trimmed and lowercase.
list: {
type: 'array',
items: { type: 'string' },
clean: (list) => list.map((item) => item.trim().toLowerCase())
},
});Dynamic field properties
Almost all field properties accept a function, it is useful to return a constraint based on some conditions. The function is called with a single argument representing the current context (data) being validated by the schema.
import { Schema } from '@jalik/schema';
const isPublishing = function (context) {
// context refers to the data being validated
return context.status === 'published';
};
const PostSchema = new Schema({
title: {
type: 'string',
required: isPublishing
},
text: {
type: 'string',
required: isPublishing
},
status: {
type: 'string',
required: true,
allowed: ['published', 'draft']
}
});
// So this is valid
PostSchema.validate({
title: 'Hello World',
text: 'This is a hello world post !',
status: 'published'
});
// And this is valid too..
PostSchema.validate({
status: 'draft'
});
// But this is not valid !
PostSchema.validate({
title: 'Hello World',
text: null,
status: 'published'
});Changelog
History of releases is in the changelog.
License
The code is released under the MIT License.
1 year ago
2 years ago
2 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
4 years ago
4 years ago
4 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
6 years ago
6 years ago
6 years ago
6 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
8 years ago
8 years ago
8 years ago