Search code examples
node.jsvalidationschemajoi

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?


Solution

  • 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'
        }
    };