DEV Community

Diogo Kollross
Diogo Kollross

Posted on

4

Dynamically required fields in Joi

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') }
Enter fullscreen mode Exit fullscreen mode

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') }
Enter fullscreen mode Exit fullscreen mode

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(),
    }),
  });
};
Enter fullscreen mode Exit fullscreen mode

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
  );
}
Enter fullscreen mode Exit fullscreen mode

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!

SurveyJS custom survey software

Build Your Own Forms without Manual Coding

SurveyJS UI libraries let you build a JSON-based form management system that integrates with any backend, giving you full control over your data with no user limits. Includes support for custom question types, skip logic, an integrated CSS editor, PDF export, real-time analytics, and more.

Learn more

Top comments (0)

SurveyJS custom survey software

JavaScript UI Libraries for Surveys and Forms

SurveyJS lets you build a JSON-based form management system that integrates with any backend, giving you full control over your data and no user limits. Includes support for custom question types, skip logic, integrated CCS editor, PDF export, real-time analytics & more.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay