What is the difference between alternatives().conditional() and any().when()?
Asked Answered
M

1

1

Straight from the docs, alternatives.conditional:

Adds a conditional alternative schema type, either based on another key value, or a schema peeking into the current value

any.when:

Adds conditions that are evaluated during validation and modify the schema before it is applied to the value

How are they different? Specifically, when will the result of one differ from the result of the other?


I found in alternatives.conditional docs a note about any.when:

Note that alternatives.conditional() is different than any.when(). When you use any.when() you end up with composite schema of all the matching conditions while alternatives.conditional() will use the first matching schema, ignoring other conditional statements.

But I don't understand, what does it mean?


References:

Mccollough answered 9/10, 2022 at 10:3 Comment(0)
C
3

Look at this phrase:

alternatives.conditional() will use the first matching schema, ignoring other conditional statements.

If you have multiple conditionals() only the first matching one affects the schema of the key.

Joi.object({
  a: Joi.alternatives()
    .conditional("c", { is: Joi.number().min(10), then: Joi.forbidden() })
    .conditional("b", {
      is: Joi.exist(),
      then: Joi.valid("y"),
      otherwise: Joi.valid("z"),
    }),
  b: Joi.any(),
  c: Joi.number(),
});

For the above schema, if the object does not have key c less than 10, only then the second conditional comes into effect. Meaning

{ a : 'z' , c : 11 , b : 2 } will say , "a" is not allowed (first condition). But { a : 'z' , c : 7 , b : 2 } will say , "a" must be [y] (second condition).

If you tweak the above condition to do something like this (I have only added an otherwise clause):

Joi.object({
  a: Joi.alternatives()
    .conditional("c", {
      is: Joi.number().min(10),
      then: Joi.forbidden(),
      otherwise: Joi.valid("x"),
    })
    .conditional("b", {
      is: Joi.exist(),
      then: Joi.valid("y"),
      otherwise: Joi.valid("z"),
    }),
  b: Joi.any(),
  c: Joi.number(),
});

then you have an invalid schema with the error: Unreachable condition. Because here the first condition will definitely result in something (It has both then and otherwise).

But according to this:

When you use any.when() you end up with composite schema of all the matching conditions

With any.when() the whole chain is read until end, to decide the final schema.

const schema = {
  a: Joi.any()
    .valid("x")
    .when("b", {
      is: Joi.exist(),
      then: Joi.valid("y"),
      otherwise: Joi.valid("z"),
    })
    .when("c", { is: Joi.number().min(10), then: Joi.forbidden() }),
  b: Joi.any(),
  c: Joi.number(),
};

when b exists, then y is a valid value for a, but when c is a number greater than equal to 10 then a is not allowed.

For example below is a valid object for above schema:

{
  a: 'y', //'x' also works
  b: 20,
  c: 9
}

but this is not:

{
  a: 'y',
  b: 20,
  c: 10
}

Also, alternatives add an extra alternative to try and do not affect the original type.

Look at the below schema:

Joi.object({
  a: Joi.alternatives(["x"]).conditional("b", {
    is: true,
    then: Joi.forbidden(),
  }),
  b: Joi.boolean(),
});

{ b: true, a: 'x' } will pass it. Joi.forbidden() cannot affect the fact that x is on of the valid values. If you pass { b: true , a: 'y' }, this will definitely fail and you will see "a" is not allowed.

If you write the above rule with when:

Joi.object({
  a: Joi.valid("x").when("b", { is: true, then: Joi.forbidden() }),
  b: Joi.boolean(),
});

then, { b : true, a: 'x' } will fail as expected because when can affect the overall type of the key.

Countermove answered 9/10, 2022 at 10:38 Comment(5)
Ok, so they're mainly different when you chain their return values (conditional().conditional() vs when().when()). Are they at all different if composite? (conditional(conditional()) vs when(when()))?Mccollough
I mean the second part of the answer also plays a role. alternatives.conditional() can not affect the key, it can only give more alternatives whereas while can alter the whole keys schema.Countermove
Do have a look at this : joi.dev/tester to play aroundCountermove
When will { a: Joi.boolean(), b: Joi.boolean(), c: Joi.alternatives().conditional("a", { is: true, then: Joi.any(), otherwise: Joi.alternatives().conditional("b", { is: true, then: Joi.any(), otherwise: Joi.string().required() }) }) } behave different from { a: Joi.boolean(), b: Joi.boolean(), c: Joi.any().when("a", { is: true, then: Joi.any(), otherwise: Joi.any().when("b", { is: true, then: Joi.any(), otherwise: Joi.string().required() }) }) }?Mccollough
lets narrow it down, why does order matter for alternatives.conditional but not for any.when? #74005411Mccollough

© 2022 - 2024 — McMap. All rights reserved.