Stripping unknown keys when validating with Joi - javascript

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.

Related

How to make custom error message using Joi?

How to make a custom message using joi? i saw many answered question related on this but i dont know why it didnt work on my end, the error message always appeared is "Student" does not contain 1 required value(s) what i want is "Student" This field is required.
export const VALIDATION_SCHEMA = {
students: Joi.array()
.label('Student Name(s)')
.items(
Joi.object({
name: Joi.string(),
value: Joi.string()
}).required().messages('"Student" This field is required.')
),
}
You can return a custom error object using Error constructor like this:
var schema = Joi.object().keys({
firstName: Joi.string().min(4).max(8).required().error(new
Error('error message here for first name')),
lastName: Joi.string().min(5).max(1).required().error(new
Error('error message here for last name'))
});
Joi.validate(req.body, schema, function(err, value) {
if (err) {
console.log(err.message)
return catched(err.message);
}
});
The easiest way in my opinion would be this.
const Joi = require("#hapi/joi");
export const categorySchema = Joi.object({
mobile: Joi.string().trim().regex(/^[6-9]\d{9}$/).required().messages({
"string.base": `"" should be a type of string`,
"string.empty": `"" must contain value`,
"string.pattern.base": `"" must be 10 digit number`,
"any.required": `"" is a required field`
}),
password: Joi.string().trim().required().messages({
"string.base": `"" should be a type of 'text'`,
"string.pattern.base": `"" must be 10 digit number`,
"any.required": `"" is a required field`
}),
}).required();

XOR validation using Joi-browser

I am using joi-browser 13.4.0. In order to generate error message for each input field I am trying to validate fields using .required() like so:
config = {
input1: Joi.string()
.empty("")
.required(),
input2: Joi.string()
.empty("")
.required()
};
schema = Joi.object(this.config).xor("input1", "input2");
But this example is invalid because when input1 or input2 is set to .required(), .xor() function is being ignored. Is there any other way to implement XOR validation without using .xor() method?
Thanks.
You don't need required() if you're using xor:
config = {
input1: Joi.string().empty(""),
input2: Joi.string().empty("")
};
schema = Joi.object(config).xor("input1", "input2");
In fact, using required() like that would never validate. You'd get one of the following error messages:
ValidationError: child "input1" fails because ["input1" is required]
or
ValidationError: "value" contains a conflict between exclusive peers [input1, input2]
Use object.length()
Is there any other way to implement XOR validation without using .xor() method?
Yes, you could for example use the object().length() property to limit the keys in an object to 1.
const Joi = require('joi-browser')
const schema = Joi.object().keys({
input1: Joi.string().empty(''),
input2: Joi.string().empty('')
}).required().length(1);
const value = {
input1: "input1",
};
// this will fail
// const value = {};
// this will fail too
// const value = {
// input1: 'input1',
// input2: 'input2',
// };
const result = Joi.validate(value, schema);
console.log(JSON.stringify(result.error, null, 2));
Be careful
Don't forget to add required() to the parent object, otherwise it is possible to pass undefined to the validation function!
Without required() on the parent it is possible that a simple undefined will pass the validation:
const Joi = require('joi-browser')
const schema = Joi.object().keys({
input1: Joi.string().empty(''),
input2: Joi.string().empty('')
}).length(1); // no required()
const value = undefined; // this will pass validation
const result = Joi.validate(value, schema);
console.log(JSON.stringify(result.error, null, 2));

How to run custom validation function in the joi schema

I want to make a field mandatory or optional based on something that is not in the schema. For example, something stored in the global scope. This is the Joi schema:
first_name: Joi.string().min(2).max(10).regex(Regex.alphabeta, 'alphabeta').error(JoiCustomErrors)
How to make first_name be required if some_global_scope_var is 1, else, make it optional?
Note: some_global_scope_var is not part of the schema.
Access Global scope with $ character
Normally you would access a relative property last_name without a prefix. For global access prefix your variable with $.
Complete Solution
const Joi = require('#hapi/joi');
global.app = {
someValue: 1,
};
const schema = Joi.object().keys({
first_name: Joi.string()
.min(2)
.max(10)
.when('$app.someValue', {
is: Joi.equal(1),
then: Joi.required(),
})
});
const value = {
first_name: 'a1300',
};
const result = schema.validate(value);
console.log(JSON.stringify(result.error, null, 2));
P.S. I omitted regex(Regex.alphabeta, 'alphabeta').error(JoiCustomErrors)

Mongoose - How to set default value if new value is not in available enum list

I have the following schema:
const userSchema = new mongoose.Schema({
profile: {
name: {
type: String,
default: "",
enum: ["", "Mike", "John", "Bob"]
}
}
}
I would like to ensure that when a user.save action is triggered and provided name variable is not in the list of available enum values, validation does not fail, but sets the value to default.
For example, in Node:
User
.findById(req.user.id)
.then(user => {
user = Object.assign(user, { name: "Sam" })
return user.save()
})
This will fail validation with Sam is not a valid enum value for path profile.name, but the ask is to have value fallback to an empty string:
{
name: ""
}
I tried tapping into Mongoose pre validate hook, but cannot seem to access provided values and documentation has not been helpful.
maybe you can use a pre save hooks
var schema = new Schema(..);
schema.pre('save', function(next) {
// verify here
next();
});

Obtain an object after Objects Destructuring

I have the following object:
original = {userName: "pepe", pass: "password", otherFields: "blabla"}
I want to destructure it to obtain another object with only one field: userName
If I do:
const {userName} = original
console.log(JSON.stringify(userName)) ---> "pepe", but I would like to obtain {userName: "pepe"}
Or
var newObj = {userName} = original
console.log(JSON.stringify(newObj)) ---> {userName: "pepe", pass: "password", otherFields: "blabla"}
I would like to obtain {userName: "pepe"} after running JSON.stringify(...) because it makes me easier to do a fetch with this data in the body part.
The only way that I found to do that is the following:
const _body = {}
_body.userName = original.userName
body: (JSON.stringify(_body))
But when I have more fields to send in the body, I need to add lines to this code. Is there a better way to do what I want?
Essentially when you destructure the value from the object you're getting just that...the value. So in this case userName will return the string "pepe". You'll have to pass in a new object literal into your stringify call to get the desired result:
const original = { userName: "pepe", pass: "password", otherFields: "blabla" };
const { userName } = original;
console.log(JSON.stringify({ userName }));
If you are going to eventually need more properties, you could use the rest operator to do this.
const original = {userName: "pepe", pass: "password", otherFields: "blabla"}
const {pass, otherFields, ...rest} = original
console.log(JSON.stringify(rest))
Only thing is with this route, if you have properties inside original that you may not want into the stringify, you will have to add them to the destructuring so the "rest" operator doesn't pick them up.

Categories

Resources