Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Joi validation - how to require or optional field based on another key exists in array?

I have this Joi schema:

  let schema = {};

  let stations = {
    contact: {
      first_name: Joi.string().min(2).max(10).regex(Regex.alphabeta, 'alphabeta').allow("").error(JoiCustomErrors),
      last_name: Joi.string().min(2).max(10).regex(Regex.alphabeta, 'alphabeta').allow("").error(JoiCustomErrors),
      phone: Joi.string().min(10).max(10).regex(Regex.num, 'num').allow("").error(JoiCustomErrors),
    },
    address: {
      place: Joi.string().min(2).max(10).regex(Regex.alphanum, 'alphanum').required().error(JoiCustomErrors),
      city: Joi.string().min(2).max(30).required().error(JoiCustomErrors),
      street: Joi.string().min(2).max(30).regex(Regex.alphabeta, 'alphabeta').required().error(JoiCustomErrors),
      house_number: Joi.string().min(1).max(6).regex(Regex.alphanum, 'alphanum').allow("").error(JoiCustomErrors)
    },
    passengers_amount: Joi.number().min(0).max(4).required().error(JoiCustomErrors),
    notes: Joi.string().min(2).max(100).regex(Regex.alphanum, 'alphanum').allow("").error(JoiCustomErrors)
  };
  schema.stations = Joi.array().items(stations).min(1).max(5).required().error(JoiCustomErrors);

As you can see, schema.stations is an array of min 1 and max 5 elements. I want that each of the elements will have the field of "address.place" only if the "contact.first_name" AND "contact.last_name" AND "contact.phone" exists (or filled properly based on the schema).

How can I do that?

like image 905
Raz Buchnik Avatar asked Jun 03 '19 08:06

Raz Buchnik


People also ask

How do I validate the data in Joi?

The validation is done using the Joi.validate () method, with the following signature: data: the data to validate which in our case is req.body. schema: the schema with which to validate the data. options: an object that specifies the validation options. Here are the validation options we used:

How to create Object schemas in Joi?

It is recommended that you create object schemas using Joi.object () or Joi.object ().keys (). When using any of these two methods, you can further control the keys that are allowed in the object using some additional constraints, which will not be possible to do using the object literal method.

How to create validation schemas for the API routes in Joi?

After familiarizing yourself with constraints and schemas in Joi, you can now create the validation schemas for the API routes. Create a new file named schemas.js in the project route directory: Start by requiring Joi: The /people endpoint will use personDataSchema. In this scenario, an administrator is creating accounts for teachers and students.

How do I set up a REST API for Joi?

You will create a REST API for this tutorial using Express to test your Joi schemas. To begin, open your command line terminal and create a new project directory: Then navigate to that directory: Run the following command to set up a new project: And install the required dependencies:


2 Answers

You can use the Joi.when() method and create a schema like this:

Joi.object().keys({
    contact: Joi.object().keys({
        first_name: Joi.string(),
        last_name: Joi.string(),
        phone: Joi.string(),
    }),
    address: Joi.object().keys({
        place: Joi.string(),
        city: Joi.string().min(2).max(30),
        street: Joi.string(),
        house_number: Joi.string()
    }).when('contact', {
        is: Joi.object().keys({
            first_name: Joi.exist(),
            last_name: Joi.exist(),
            phone: Joi.exist(),
        }),
        then: Joi.object({ place: Joi.required() }).required(),
        otherwise: Joi.object({ place: Joi.forbidden() })
    }),
    passengers_amount: Joi.number(),
    notes: Joi.string()
});

I just simplified your schema so it's easy to understand.

Basically, what we are saying here is, if contact.first_name, contact.last_name and contact.phone exists, then address and address.place are required, otherwise address.place is forbidden.

For instance, this object will fail, because address does not exist:

{
    contact: {
        first_name: 'a',
        last_name: 'b',
        phone: 'c'
    }
}

and this will fail because address.place does not exist:

{
    contact: {
        first_name: 'a',
        last_name: 'b',
        phone: 'c'
    },
    address: {
    }
}

Finally, according to the schema defined, this object will pass:

{
    contact: {
        first_name: 'a',
        last_name: 'b',
        phone: 'c'
    },
    address: {
        place: 'd'
    }
};
like image 195
soltex Avatar answered Oct 19 '22 00:10

soltex


Thanks to Soltex, thats the right schema that should be used (but please refer to the changes I have made):

Joi.object().keys({
    contact: {
      first_name: Joi.string().min(2).max(10).regex(Regex.alphabeta, 'alphabeta').allow("").error(JoiCustomErrors),
      last_name: Joi.string().min(2).max(10).regex(Regex.alphabeta, 'alphabeta').allow("").error(JoiCustomErrors),
      phone: Joi.string().min(10).max(10).regex(Regex.num, 'num').allow("").error(JoiCustomErrors),
    },
    address: Joi.object().keys({
      place: Joi.string().min(2).max(10).regex(Regex.alphanum, 'alphanum').error(JoiCustomErrors),
      city: Joi.string().min(2).max(30).required().error(JoiCustomErrors),
      street: Joi.string().min(2).max(30).regex(Regex.alphabeta, 'alphabeta').required().error(JoiCustomErrors),
      house_number: Joi.string().min(1).max(6).regex(Regex.alphanum, 'alphanum').allow("").error(JoiCustomErrors)
    }).when('contact', {
      is: Joi.object().keys({
        first_name: Joi.string().min(1),
        last_name: Joi.string().min(1),
        phone: Joi.string().min(1),
      }),
      then: Joi.object({ place: Joi.required() }).required(),
      otherwise: Joi.object({
        place: Joi.optional().allow("")
      })
    }),
    passengers_amount: Joi.number().min(0).max(4).required().error(JoiCustomErrors),
    notes: Joi.string().min(2).max(100).regex(Regex.alphanum, 'alphanum').allow("").error(JoiCustomErrors)
  })

Please note that changes from my answer to Soltex's answer: he made the when "contact.first_name" "contact.last_name" "contact.phone" to be: Joi.exists(). That is not good, since in such a way even an empty object is "exists" and then require the user to provide the "address.place". We don't want such a thing, we need at leat one char in each of those fields.

Plus, the otherwise statement in Soltex's answers is using the Joi.forbidden() while this is not the desired behaviour in here - we still need to allow the user to provide place, even without a contact, but this shouldn't be mandatory - so I used the: Joi.optional() instead.

like image 34
Raz Buchnik Avatar answered Oct 18 '22 23:10

Raz Buchnik