Introduction
Joi is a popular JavaScript validation library. Its fluent and compact interface makes it easy to describe large, complex, validation schemas. Here's a small example:
import Joi from 'joi';
const schema = Joi.object({
user: Joi.string().required(),
pass: Joi.string().required(),
address: Joi.object({
street: Joi.string(),
number: Joi.number(),
}),
});
const result1 = schema.validate({ user: 'joe', pass: '123' });
// { value: { user: 'joe', pass: '123' } }
const result2 = schema.validate({ user: 'joe', pass: false });
// { error: ValidationError('"user" is required') }
The Problem
Joi has support for conditionally changing parts of the schema based on (for example) the value or presence of some of the other object fields using the when method.
The following example requires pass
only when user
is present in the object:
const schema = Joi.object({
user: Joi.string(),
pass: Joi.string().when('user', {
is: Joi.exist(), then: Joi.required()
}),
});
const result1 = schema.validate({});
// { value: {} }
const result2 = schema.validate({ user: 'joe' });
// { error: ValidationError('"pass" is required') }
But what if you want to change some field validation rules based on a condition external to the validated data? You could duplicate the schema and only change whatever is different in each case.
In this example, fields such as pass
and address.street
are only required when validating the payload of version 2 of our API (because they were optional in version 1):
if (apiVersion === 2) {
schema = Joi.object({
user: Joi.string().required(),
pass: Joi.string().required(),
address: Joi.object({
street: Joi.string().required(),
number: Joi.number(),
}).required(),
});
} else {
schema = Joi.object({
user: Joi.string().required(),
pass: Joi.string(),
address: Joi.object({
street: Joi.string(),
number: Joi.number(),
}),
});
};
This works for small schemas, but it can get wild when working with huge objects wit differences scattered along several nested fields.
The Solution
I was almost giving up when I found the fork method. It creates a modified schema by running a custom callback over the schema fields you specify.
The following example is just like the previous one, but uses the fork
method to modify the schema:
schema = Joi.object({
user: Joi.string().required(),
pass: Joi.string(),
address: Joi.object({
street: Joi.string(),
number: Joi.number(),
}),
});
if (apiVersion === 2) {
const makeRequired = (x) => x.required();
schema = schema.fork(
['pass', 'address', 'address.street'],
makeRequired
);
}
I'm no specialist in Joi, but hope that this helps you. Drop a comment if you know a different way to handle this use case!
Top comments (0)