Array of objects with different schemas - JOI - javascript

I currently have the following schema on my app:
Joi.object().keys({
users: Joi.array().items(mySchema)
})
So I can get an array of users and validate them.
But now I need different schemas for each object.
Is there a way I can do something like:
Joi.object().keys({
users: [
Joi.object().keys(mySchemaForUserOne),
Joi.object().keys(mySchemaForUserTwo),
// ...
]
})

Here you go ~
Joi.object().keys({
users: Joi.array().items(
Joi.alternatives()
.conditional('.type', {
switch: [{
is: 'mySchemaForUserOne',
then: Joi.object({ ... }),
}, {
is: 'mySchemaForUserTwo',
then: Joi.object({ ... }),
}],
})
)
})
You can check documents here =>
Joi conditional API

You can use array.items by listing all allowed types. If a given type is .required() then there must be a matching item in the array: joi API reference
users: Joi.array().items(Joi.object().keys(mySchemaForUserOne).required(),
Joi.object().keys(mySchemaForUserTwo).required(),
// ...)

Related

How can I have multiple unique checks with a Joi schema validation?

I have data:
const mockScenario1 = {
drawingNode: {
moduleRackOutputs: [
{
moduleId: 'module1',
tilt: 'tilt1',
rack: {
framingType: 'framing1'
}
},
{
moduleId: 'module2',
tilt: 'tilt1',
rack: {
framingType: 'framing1'
}
}
]
}
}
I want to ensure that:
If there are different moduleId values, I want: Only one module allowed
If there are different rack.framingType values, I want: Only one framing type allowed
I have this sort of started with:
Joi.object({
drawingNode: Joi.object({
moduleRackOutputs: Joi.array()
.items(
Joi.object().keys({
moduleId: Joi.string().required(),
tilt: Joi.string().required(),
rack: Joi.object({
framingType: Joi.string().required()
})
})
)
.unique((a, b) => a.moduleId !== b.moduleId)
.messages({
'array.unique':
'The drawing contains more than one module type. Multiple module types are not yet supported by the PVsyst RPA.'
})
})
})
Which works for the module, but not the framingType. Seems I can't use multiple unique?
I'd love any help or pointers. Thanks!
Here is the Solution. I hope it would help.
Joi.object({
drawingNode: Joi.object({
moduleRackOutputs:
Joi.array().unique('moduleId').unique('rack.framingType')
.messages({
'array.unique':
'The drawing contains more than one module type. Multiple module types are not yet supported by the PVsyst RPA.'
})
})
})
OR
Joi.object({
drawingNode: Joi.object({
moduleRackOutputs: Joi.array()
.items(
Joi.object().keys({
moduleId: Joi.string().required(),
tilt: Joi.string().required(),
rack: Joi.object({
framingType: Joi.string().required()
})
})
)
.unique((a, b) => a.moduleId === b.moduleId || a.rack.framingType === b.rack.framingType)
.messages({
'array.unique':
'The drawing contains more than one module type. Multiple module types are not yet supported by the PVsyst RPA.'
})
})
})

Complex validation using Joi library

I have this json:
let purchaseSubscription = {
'metadata': {
'eventName': 'PurchaseSubscription',
'type': 'setup' // setup, repurchase or recurring
},
'data': {
'subscriptionId': '447481',
'subscriptionTrialId': '23542'
}
};
If the metadata.type has value setup
then data.subscriptionTrialId should be validated for existence and to be a number.
If the metadata.type has other values, the data.subscriptionTrialId can be ignored.
This is what I currently have:
const Joi = require('joi');
const validTypes = ['setup', 'repurchase', 'recurring'];
exports.schema = Joi.object().keys({
metadata: Joi.object({
eventName: Joi.string().required(),
type: Joi.string().valid(validTypes).required()
}).required(),
data: Joi.object({
subscriptionId: Joi.number().integer().min(1).max(2147483647).required(),
subscriptionTrialId: Joi.when(
'metadata.type', { is: 'setup', then: Joi.required() })
}).required()
}).options({ 'allowUnknown': true });
But I am not getting desired results. The data.subscriptionTrialId is always validated, no matter what I have under metadata.type
I tried reading documentation, but can't make it to work :(
You can use the otherwise key in the JOI schema.
Somewhere in your code before declaring exports.schema:
const trialIdRequired = Joi.object({
subscriptionId: Joi.number().integer().min(1).max(2147483647).required(),
subscriptionTrialId: Joi.required()
}).required()
const trialIdNotRequired = Joi.object({
subscriptionId: Joi.number().integer().min(1).max(2147483647).required(),
subscriptionTrialId: Joi.any()
})
And then add a when clause to the data field
data: Joi.when(
'metadata.type',
{
is: 'setup',
then: trialIdRequired,
otherwise: trialIdNotRequired
})

Joi array Object validation based on root key value

I have a complex scenario that I want to validate using Joi
here sample Joi Object Schema
const itemSchema = Joi.object({
product_id: Joi.string().required(),
quantity: Joi.number().required().min(0)
});
let objSchema = {
items: Joi.array().items(itemSchema).required().min(1),
item_return_flag: Joi.string().optional().valid(true, false)
};
depending opon item_return_flag key value true or false, I want to change the items.quantity min value requirement. When true, quantity will be 0 , otherwise it will be 1.
Is there anyway, to control the definition of validation of the object in an array, based on the root object in Joi
The sample code that will switch the schema based one the parent key item_return_flag. Schema of the array need to switch based using Joi.altertnatives()
let itemArr = Joi.object({
product_id: Joi.string().required(),
quantity: Joi.number().required().min(0)
});
let itemArr2 = Joi.object({
product_id: Joi.string().required(),
quantity: Joi.number().required().min(1)
});
let itemSchema = Joi.alternatives()
.when('item_return_flag', { is: true, then: Joi.array().items(itemArr).required().min(1), otherwise: Joi.array().items(itemArr2).required().min(1)}) ;
let objSchema = {
items: itemSchema,
item_return_flag: Joi.string().optional().valid(true, false)
};
It looks to me like you could, following the API docs, do something like this:
let objSchema = {
items: Joi.array().items(Joi.object({
product_id: Joi.string().required(),
quantity: Joi.alternatives().when('item_return_flag', {
is: true, then: Joi.number().required().min(0),
otherwise: Joi.number().required().min(1)
})
})).required().min(1),
item_return_flag: Joi.string().optional().valid(true, false)
};
I'm not 100% sure that's the exact correct structure, but it's close. The Joi.alternatives() is provided for just such use cases.

Joi nested schemas and default values

I'm trying to get Joi to enforce default values on a secondary schema referenced by another. I have two schemas like so:
const schemaA = Joi.object().keys({
title: Joi.string().default(''),
time: Joi.number().min(1).default(5000)
})
const schemaB = Joi.object().keys({
enabled: Joi.bool().default(false),
a: schemaA
})
What I want is to provide an object where a is not defined and have Joi apply the default values for it instead like this:
const input = {enabled: true}
const {value} = schemaB.validate(input)
//Expect value to equal this:
const expected = {
enabled: true,
a: {
title: '',
time: 5000
}
}
The problem is that since the key is optional it is simply not enforced. So what I want is for it to be optional yet properly filled with schemaA defaults if not present. I've been looking through the documentation, but can't seem to find any info on this though I'm probably missing something obvious. Any tips?
Update : April, 2020.
Now, you can use default() in nested objects. Here is the commit in repo with test.
var schema = Joi.object({
a: Joi.number().default(42),
b: Joi.object({
c: Joi.boolean().default(true),
d: Joi.string()
}).default()
}).default();
This should do it:
const schemaA = Joi.object().keys({
title: Joi.string().default(''),
time: Joi.number().min(1).default(5000),
});
const schemaB = Joi.object().keys({
enabled: Joi.bool().default(false),
a: schemaA.default(schemaA.validate({}).value),
});
Although it would be much better if they would implement a feature to let us pass in Joi schema objects for defaults, like so: schemaA.default(schemaA) or schemaA.default('object')

Stripping unknown keys when validating with Joi

I'm using Joi to validate a JavaScript object in the server. The schema is like the following:
var schema = Joi.object().keys({
displayName: Joi.string().required(),
email: Joi.string().email(),
enabled: Joi.boolean().default(false, "Default as disabled")
}).unknown(false);
The schema above will report an error if there is an unknown key in the object, which is expected, but what I want is to strip all the unknown silently, without an error. Is it possible to be done?
You need to use the stripUnknown option if you want to strip the unknown keys from the objects that you are validating.
cf options on https://github.com/hapijs/joi/blob/master/API.md#validatevalue-schema-options-callback
As in Version 14.3.4, there is a simple solution to this issue. Here is the code that solves the problem for you.
// Sample data for testing.
const user = {
fullname: "jayant malik",
email: "demo#mail.com",
password: "password111",
username: "hello",
name: "Hello"
};
// You define your schema here
const user_schema = joi
.object({
fullname: joi.string().min(4).max(30).trim(),
email: joi.string().email().required().min(10).max(50).trim(),
password: joi.string().min(6).max(20),
username: joi.string().min(5).max(20).alphanum().trim()
})
.options({ stripUnknown: true });
// You validate the object here.
const result = user_schema.validate(user);
// Here is your final result with unknown keys trimmed from object.
console.log("Object with trimmed keys: ", result.value);
const joi = require('joi');
joi.validate(object, schema, {stripUnknown:true}, callback);
Here is the current way to include the strip unknown option:
const validated = customSchema.validate(objForValidation, { stripUnknown: true });
If you pass in an objForValidation that has a key which isn't defined in your customSchema, it will remove that entry before validating.

Categories

Resources