Express-validator test item's array independantly - javascript

I'm trying to add a validation chain on an object which contain an array;
this array contain objects that have to be tested differently according to one of the field of the object,
here is an example :
{
"title" : "my title",
"sections" : [
{
"type": "A",
"optionsA" : {...}
},
{
"type": "B",
"optionsB" : {...}
},
{
"type": "A",
"optionsA" : {...}
},
{
"type": "C",
"optionsC" : {...}
},
]
}
is there a way to achieve this ? i tried to use OneOf() with specific test for each section but with no success

while this solution is a bit more work I think it's the most straightforward and maintainable.
Download the Validator & Is-emptypackages
Using this package you can validate and JSON object with a lot of control. So I use it like this to validate my req.body
const Validator = require('validator');
const isEmpty = require('is-empty');
module.exports = function validateRegiterInput(data) {
let errors = {};
//Convert empty fields to a empty string so we can use validator functions
data.firstName = !isEmpty(data.firstName) ? data.firstName : "";
data.lastName = !isEmpty(data.lastName) ? data.lastName : "";
data.email = !isEmpty(data.email) ? data.email : "";
data.password = !isEmpty(data.password) ? data.password : "";
//school, schoolname? tbd
//Name check
if (Validator.isEmpty(data.firstName)) {
errors.firstName = "First name is required!"
}
if (Validator.isEmpty(data.lastName)) {
errors.lastName = "Last name is required!"
}
//Email check
if (Validator.isEmpty(data.email)) {
errors.email = "Email field is required!";
} else if (!Validator.isEmail(data.email)) {
errors.email = "Email is invalid!";
}
//Password check
if (Validator.isEmpty(data.password)) {
errors.password = "Password field is required!";
}
if (!Validator.isLength(data.password, { min: 6, max: 30 })) {
errors.password = "Password must be at least 6 characters!";
}
return {
errors,
isValid: isEmpty(errors)
};
};
So what happens is, I call this function with my data and im able to nest into arrays or objects just using the dot notation.
In the main file, I have this
const { errors, isValid } = validateRegisterInput(req.body);
If there were any errors then they'll be in the errors object. and is valid will be false. Otherwise, if it's empty the is valid is true and you're good to go. I hope this helped. If you need more clarification I'm here to help

Related

Validate json with given keys in Javascript and Reactjs

I have below JSON ,
[
{
"name":"john",
"school":"school 2",
"address":"newyork"
},
{
"name":"peter",
"school":"school 1",
"address":"washington"
}
]
here i want to validate below mentioned things,
1 - it should be an array
2 - it must have only 3 fields (name,school,address) not more that or less than these three fields
3 - "school" can be either 'school1' or 'school2' and "address" can be either "newyork" or "washington"
I amneed to do this using react js and javascript
Thanks in advance
Validation using yup
const schema = yup.array()
.of(
yup.object().shape({
name: yup.string().required("Required"),
school: yup.mixed().oneOf(['school 1','school 2']),
address: yup.mixed().oneOf(['newyork','washington'])
}).noUnknown(true)
)
and validate,
await schema.validate(your_object).catch(function (err) {
err.name; // => 'ValidationError'
err.errors;
});
Note: this validation is not tested
Here is a simplified function of what you are trying to do
const validateJSON = (json) => {
if (!Array.isArray(json)) {
return false;
}
for (const element of json) {
if (Object.keys(element).length !== 3) {
return false;
}
if (element.school !== "school 1" && element.school !== "school 2") {
return false;
}
if (element.address !== "newyork" && element.address !== "washington") {
return false;
}
}
return true;
};
then just use const isValid = validateJSON(json);

If else and switch case alternative in Javascript

Looking for an alternative to the conditional statement. As you can see from my code, the process is overly repetitious and disorganized. It will become increasingly difficult to maintain code as it grows in size. In order to avoid this situation, I'm looking for alternatives.
function validate(values) {
let errors = {};
// Email Error
if (!values.email) {
errors.email = "Email address is required";
} else if (!/\S+#\S+\.\S+/.test(values.email)) {
errors.email = "Email address is invalid";
}
// Password Error
if (!values.password) {
errors.password = "Password is required";
} else if (values.password.length < 6) {
errors.password = "Password must be 6 or more characters";
}
return errors;
}
You could move some logic into configuration. Try to harmonise checks such that they all depend on regular expressions. So for a minimum length of 6, use /....../ as regular expression. Also make sure the regular expression will not accept an empty string in case the field is considered required.
For example:
// All specifics are encoded here:
const checks = [
{ field: "email", regex: /^\S+#\S+\.\S+$/, name: "Email address", msg: "must be 6 or more characters" },
{ field: "password", regex: /....../, name: "Password", msg: "is invalid" },
];
// ...while this is now (more) generic:
function validate(values) {
const errors = {};
for (const {field, regex, name, msg} of checks) {
if (!regex.test(values[field])) {
errors[field] = name + " " + (values[field] ? msg : "is required");
}
}
return errors;
}
You can access column in a js object by using myObject[nameOfYourColumn]
So we can think build a generic method like this
function validateColumn(object, columnName, format, errors) {
if (!object[columnName]) {
errors[columnName] = `${columnName} is required`;
} else if (!format.test(object[columnName])) {
errors[columnName] = `${columnName} is invalid`;
}
}
your method will become
function validate(values) {
let errors = {};
// Email Error
validateColumn(values, 'email', '/\S+#\S+\.\S+/', errors);
// Password Error
validateColumn(values, 'password', '/^.{6,}$/', errors);
return errors;
}
Right now, the validate function is used for both passwords and emails.
However, this can be split into two functions, one for validating emails, and the other for validating passwords. This helps to decouple the action of validating emails from validating passwords, and makes debugging/maintaining easier.
Also, you can try ternary operators if you want to make your if-else clauses less cluttered.
function validate(values) {
errors = {};
errors.email = validate_email(values.email) == null
? null : validate_email(values.email);
errors.password = validate_password(values.password) == null
? null : validate_password(values.password);
return errors;
}
function validate_email(email) {
if (email == null) {
return "Email address is required";
} else if (!/\S+#\S+\.\S+/.test(values.email)) {
return "Email address is invalid";
}
return null;
}
//validate_password left as an exercise

Iterating a multi-dimensional object with es6

Are there any methods on how can I iterate between object keys
var dataSubmit = {
email : {
value : email,
rules : 'required email'
},
password : {
value : password,
rules : 'required,min:6'
}
};
I was hoping I can access it like
each (data in dataSubmit) {
console.log(data.value);
console.log(data.rules);
}
and get
email: {value : email, rules : 'required, email'};
so I can
iterator {
func(email: {value : email, rules : 'required, email'})
}
I need to keep the object as whole
for(const data of Object.values(dataSubmit))
console.log(data.value, data.rules);
Just use for..of on the objects values.
You're almost there, use the for...in statement for that - replace each with for.
var dataSubmit = {
email : {
value : 'email',
rules : 'required email'
},
password : {
value : 'password',
rules : 'required,min:6'
}
};
for (let key in dataSubmit) {
console.log(dataSubmit[key]);
}
Alternatively use the for...of statement with Object.values(dataSubmit) which returns an array of values
var dataSubmit = {
email : {
value : 'email',
rules : 'required email'
},
password : {
value : 'password',
rules : 'required,min:6'
}
};
for (let data of Object.values(dataSubmit)) {
console.log(data);
}
You could use forEach loop on Object.values.
var dataSubmit = {email: {value: "email",rules: 'required email'},password: {value: "password",rules: 'required,min:6'}};
Object.values(dataSubmit).forEach(o => {
console.log(o.value);
console.log(o.rules)
})

JSON Schema extract the required fields

I need to get a list of the required fields out of a JSON-Schema+Data.
Currently, we are using AJV to get error messages in our forms with JSON Schema and it is working great.
I need a way to get all the required fields (even if filled) in order to mark those fields with * as "required". required fields might change depending on the schema and data combinations.
Also tried hacking tv4 to extract the required fields without success.
Please help.
Example for such schema:
{
"type": "object",
"required": [
"checkbox"
],
"properties": {
"checkbox": {
"type": "boolean"
},
"textbox": {
"type": "string"
}
},
"oneOf": [
{
"required": [
"textbox"
],
"properties": {
"checkbox": {
"enum": [
true
]
}
}
},
{
"properties": {
"checkbox": {
"enum": [
false
]
}
}
}
],
"additionalProperties": false
}
Rereading your question the easiest way to do what you'd like would be to
get the Json data on page load,
iterate over the json data to remove valid values (see sample 1),
Call tv4.validateMultiple(data, schema),
check the result object and get the required fields (see sample 2).
sample 1
for(let prop in data) {
if(data.hasOwnProperty(prop) {
//set value to null, -1, or some other universally bad value
data[prop]...value = null;
}
}
sample 2
let result = tv4.validateMultiple(data, schema);
let required = result.errors;
We solved it by:
Forking tv4 (tv4 - because it was easy to edit):
https://github.com/mikila85/tv4
outputting an array of "Requireds".
We itereted each required field, emptying it's data and sending data+schema to AJV for validation (AJV and not tv4 because its faster at parsing).
By doing that we could know individually which required field is required for the given data.
these are the working functions we came out with (not the cleanest but will help get the idea)
function getAllRequiredFields() {
var allRequiredFields = tv4.validateMultiple($scope.formModel, $scope.formSchema).requireds;
allRequiredFields = allRequiredFields.filter(function onlyUnique(value, index, self) {
return self.indexOf(value) === index;
});
return allRequiredFields;
}
function getRequiredFields() {
var t0 = performance.now();
//should be called every model change because of optimization in tv4 for the data+schema.
var allRequiredFields = getAllRequiredFields();
angular.forEach(allRequiredFields, function (requiredPath) {
var modelWithDeletedRequiredProperty = angular.copy($scope.formModel);
deleteValue(modelWithDeletedRequiredProperty, requiredPath);
if (!validateForm(modelWithDeletedRequiredProperty)) {
var requiredError = getErrorObjectsArray(validateForm.errors).find(function (error) {
return error.path === requiredPath;
});
if (requiredError) {
localValidation[requiredError.inputName] = localValidation[requiredError.inputName] || {};
localValidation[requiredError.inputName].isRequired = true;
requiredFieldsPath.push(requiredError.inputName);
}
}
});
var t1 = performance.now();
console.log("form checking took " + (t1 - t0) + " milliseconds.");
}
This function grabs schema indices recursively, so maybe you could adapt it a little
// https://github.com/pubkey/rxdb/blob/master/src/rx-schema.js
export function getIndexes(jsonID, prePath = '') {
let indexes = [];
Object.entries(jsonID).forEach(entry => {
const key = entry[0];
const obj = entry[1];
const path = key === 'properties' ? prePath : util.trimDots(prePath + '.' + key);
if (obj.index)
indexes.push([path]);
if (typeof obj === 'object' && !Array.isArray(obj)) {
const add = getIndexes(obj, path);
indexes = indexes.concat(add);
}
});
if (prePath === '') {
const addCompound = jsonID.compoundIndexes || [];
indexes = indexes.concat(addCompound);
}
indexes = indexes
.filter((elem, pos, arr) => arr.indexOf(elem) === pos); // unique;
return indexes;
}

How to return matched json keys with key?

I'm trying to return and match some keys with a specific value.
If errors keys contain the "password" then I'd like to return only the ones that have password on them, in this case remove the one with firstname_empty.
I came up with this, but even after changing some logic I end up returning the all 3 of them for some reason.
var key = "password";
var errors = [{
"password_empty": "Password is empty",
"firstname_empty": "First name is required",
"password_min": "Password needs a min of 6 chars"
}];
for(var i in errors){
if(errors.match(key)){
console.log(errors[i]);
}
}
There are different ways on how to get the result you want depending on whether you want to preserve your original data structure and return a new one, or whether you want to mutate the original data.
1) Mutate the original data structure - deleting properties from an object.
var errors = [{
"password_empty": "Password is empty",
"firstname_empty": "First name is required",
"password_min": "Password needs a min of 6 chars"
}];
function byKeyword(obj, keyword) {
for (let key in obj) {
if (!key.includes(keyword)) delete obj[key];
}
return [obj];
}
let result = byKeyword(errors[0], 'password');
console.log(result);
2) A simple loop over the object adding matched properties to a temp object.
var errors = [{
"password_empty": "Password is empty",
"firstname_empty": "First name is required",
"password_min": "Password needs a min of 6 chars"
}];
function byKeyword(obj, keyword) {
const temp = {};
for (let key in obj) {
if (key.includes(keyword)) temp[key] = obj[key];
}
return [temp];
}
let result = byKeyword(errors[0], 'password');
console.log(result);
3) Similar to 2 but using reduce.
var errors = [{
"password_empty": "Password is empty",
"firstname_empty": "First name is required",
"password_min": "Password needs a min of 6 chars"
}];
function byKeyword(obj, keyword) {
return Object.keys(obj).reduce((acc, key) => {
if (key.includes(keyword)) acc[0][key] = obj[key];
return acc;
}, [{}]);
}
let result = byKeyword(errors[0], 'password');
console.log(result);
EDIT: to get the property values only you can use a loop again...
function byKeyword(obj, keyword) {
const temp = [];
for (let key in obj) {
if (key.includes(keyword)) temp.push(obj[key]);
}
return temp;
}
...or reduce.
function byKeyword(obj, keyword) {
return Object.keys(obj).reduce((acc, key) => {
if (key.includes(keyword)) acc.push(obj[key]);
return acc;
}, []);
}
Hope this was useful.
var key = "password";
var errors = [{
"password_empty": "Password is empty",
"firstname_empty": "First name is required",
"password_min": "Password needs a min of 6 chars"
}];
var resErrors = {};
for(var i in errors[0]){
if(i.match(key)){
resErrors[i] = errors[0][i];
}
}
console.log(resErrors);
You need to math the key, then store if key matched in a new object.
If you have array of object and want to remove the matched key , then loop through the array and in every loop create another object set the values and push to the result array.
var key = "password";
var errors = [{
"password_empty": "Password is empty",
"firstname_empty": "First name is required",
"password_min": "Password needs a min of 6 chars"
}, {
"password_empty": "Password is empty",
"firstname_empty": "First name is required"
}];
var resArr = [];
errors.forEach(function(erorOb) {
var resErrors = {};
for (var i in erorOb) {
if (i.match(key)) {
resErrors[i] = erorOb[i];
}
}
resArr.push(resErrors);
})
console.log(resArr);
The answer is not very clear, so I have two options.
if you want to return only the errors that ANY of the keys contain the filtered string, you could try the following
var key = "password";
var errors = [{
"password_empty": "Password is empty",
"firstname_empty": "First name is required",
"password_min": "Password needs a min of 6 chars"
}, {
"does not have it": "any value"
}];
var filtered = errors
.filter(error => Object.keys(error)
.filter(errorKey => errorKey.indexOf(key) >= 0)
.length > 0)
console.log(filtered);
If you also want to filter the error keys that contains the filter word, your could try
var key = "password";
var errors = [{
"password_empty": "Password is empty",
"firstname_empty": "First name is required",
"password_min": "Password needs a min of 6 chars"
}, {
"does not have it": "any value"
}];
var filtered = errors
.map(error => Object.keys(error)
.filter(errorKey => errorKey.indexOf(key) >= 0)
.reduce((error, errorKey, index) => {
error[errorKey] = errors[index][errorKey];
return error;
}, {})
)
.filter(error => Object.keys(error).length > 0)
console.log(filtered);
First things first:
Please make the error object is either an array or an object. After that, it should be easy to loop through it.
Here, I have implemented it as an object.
var key = "password";
var errors = {
"password_empty": "Password is empty",
"firstname_empty": "First name is required",
"password_min": "Password needs a min of 6 chars"
};
for(var err in errors){
if(err.match(key)){
console.log(err);
}
}
The 'for in' retrieves the keys of the object which is safer for testing.
Output:
password_empty
password_min

Categories

Resources