I have the following JSON objects:
{
"batch": false,
"name": "foobar"
}
{
"batch": true,
"names": ["foo", "bar", "baz"]
}
With the current implementation of Joi is it possible to make names required only if batch is true and not allowed if batch is false? Reading the docs I think no? But I'm asking because I might be missing something! :)
If not is it something that might be added to Joi or should I do that check "post validation"?
Can you suggest an API for this feature?
how about:
withvalue(value,[fieldnames])
with something like:
internals.Any.prototype.withvalue = function (testValue) {
var args = Array.prototype.slice.call(arguments);
var peers = Utils.flatten(args.slice(1));
for (var i = 0, li = peers.length; i < li; i++) {
Utils.assert(typeof peers[i] === 'string', 'peers must be a string');
}
this._test('withvalue', args, function (value, state, options) {
if(testValue !== value) {
return null;
}
if (!state.parent) {
return internals.Any.error('any.with.parent', null, state);
}
for (var i = 0, il = peers.length; i < il; ++i) {
if (!state.parent.hasOwnProperty(peers[i]) ||
state.parent[peers[i]] === undefined ||
state.parent[peers[i]] === null ||
state.parent[peers[i]] === '') {
return internals.Any.error('any.with.peer', { value: peers[i] }, state);
}
}
return null;
});
return this;
};
@HughePaul looks pretty limited for a very narrow use case. I'm looking for something more generic where you can express any condition based on this or other key values.
perhaps a requiredif([conditions]) way of looking at it?
It would need to be run after all other fields had been validated and converted.
perhaps:
title: Joi.string().required().valid('Mr','Mrs','Ms','Other'),
otherTitle: Joi.string().requiredif( {'title': Joi.string().valid('Other')} )
I am looking for something more along the lines of string().condition(some_condition, morerules()) but without needing to parse the condition.
The first thing I tried was passing an object to any.with() as in:
{
names: Joi.array().with({ batch: true })
}
That didn't work obviously. But to me that is the most obvious from an API usage perspective. I'm not familiar enough with the code itself to know wether or not that would be possible.
Again, that's a very narrow way of doing conditions.
I agree. I'm not sure I understand what you mean by not having to parse the condition though. Care to explain?
Do you mean that the condition would be a function, like:
var schema = Joi.object({
a: Joi.string()
.condition(function(){ return this.a === 'canHasB'; }, Joi.any().with('b'))
.condition(function(){ return this.a === 'canHasC'; }, Joi.any().with('c')),
b: Joi.number(),
c: Joi.number()
});
or (if, then) validation parameters
var schema = Joi.object({
a: Joi.string()
.condition(Joi.string().valid('canHasB'), Joi.any().with('b')),
.condition(Joi.string().valid('canHasC'), Joi.any().with('c')),
b: Joi.number(),
c: Joi.number()
});
of .if().then() promise style:
var schema = Joi.object({
a: Joi.string()
.if(Joi.string().valid('canHasB')).then(Joi.any().with('b'))
// or:
.if().valid('canHasC').then().with('c')
.if().valid('canHasBandC').then().with('b').with('c'),
b: Joi.number(),
c: Joi.number()
});
or perhaps chains that can fail early, but that might be difficult to work out what is a condition fail and what is an error to report:
var schema = Joi.object({
a: Joi.string().conditional(
Joi.string().valid('canHasB').with('b'),
Joi.string().valid('canHasC').with('c')
),
b: Joi.number(),
c: Joi.number()
});
Obviously there could be complex validation and complex requirements specified in those sort of formats, but I think there is also still milage in a simple .withIfValue('value','requiredField'...) format as that may cover a lot of conditional requirements I've come across building APIs.
I see the .if().then() format as the prettiest of these, and I could see it implemented as follows:
To validate these clauses the value would be validated against each 'if' chain, and if it didn't error then the value would also be validated against the 'then' chain. (with object() parents set up etc)
does this make any sense?
maybe if could take an optional field name to let you reference a value of another object property, so for the OP's issue the solution could be:
{
batch: Joi.boolean().required(),
name: Joi.any().required()
.if('batch').valid(true).then(Joi.array().includes(Joi.string()).min(1))
.if('batch').valid(false).then(Joi.string()))
}
So the definitions of if() and then() could be:
if([field_name],[joi_rule_chain]) returns joi_rule_chain or new base type rule chain
then([joi_rule_chain]) returns joi_rule_chain or new base type rule chain
Replaced by #194
This thread has been automatically locked due to inactivity. Please open a new issue for related bugs or questions following the new issue template instructions.
Most helpful comment
perhaps a requiredif([conditions]) way of looking at it?
It would need to be run after all other fields had been validated and converted.
perhaps: