Reputation: 7732
I'm looking for an good / elegant way to validate that a javascript object has the required properties, so far this is what I have:
var fields = ['name','age','address'];
var info = {
name: "John Doe",
age: "",
phone: "123-456-7890"
}
var validateFields = function(o, required_fields) {
required_fields.forEach(function(field){
if(o.hasOwnProperty(field)){
if(o[field]){
console.log(field + ": " + o[field]);
}else{
console.log(field + " exists but is empty");
}
}else{
console.log(field + " doesn't exist in object");
}
});
}
validateFields(info, fields);
Is there a more efficient / elegant way of doing this in plain javascript?
EDIT: Ok so I'm glad I asked because I had completely missed a bunch of possible conditions like zero.
With elegance out the window, how is this for a validation function? Are there any other cases I should be checking for?
var fields = ['name','age','address'];
var info = {
name: "John Doe",
age: 0,
address: false,
phone: "123-456-7890"
}
var validateFields = function(o, required_fields, null_valid, zero_valid, empty_valid) {
var invalid_fields = [];
required_fields.forEach(function(field){
if(field in o){
switch(o[field]){
case '':
console.log(field + " exists but is empty");
if(!empty_valid){
invalid_fields.push(o[field]);
}
break;
case undefined:
console.log(field + " exists but is undefined");
invalid_fields.push(o[field]);
break;
case null:
console.log(field + " exists but is null");
if(!null_valid){
invalid_fields.push(o[field]);
}
break;
case 0:
console.log(field + " exists and the value is 0");
if(!zero_valid){
}
invalid_fields.push(o[field]);
break;
default:
console.log(field + ": " + o[field]);
break;
}
}else{
console.log(field + " doesn't exist in object");
invalid_fields.push(o[field]);
}
});
return invalid_fields;
}
var invalid = validateFields(info, fields, true, true, false);
console.log(invalid);
if(invalid.length >0){
console.log("ERROR: Missing fields");
}else{
console.log("Fields are valid");
}
Upvotes: 22
Views: 46668
Reputation:
!You can do the following
const checkObj = (obj, fields) => {
if ('object' !== typeof obj || null == obj) {
console.log('Object is not valid');
return false;
}
const hasOnlyTheKeys = JSON.stringify(Object.keys(obj).sort()) === JSON.stringify(fields.sort());
if (false === hasOnlyTheKeys) {
console.log('Keys do not match');
return false;
}
for (let prop in Obj) {
switch (obj[prop]) {
case null:
case undefined:
console.log(prop + ' is undefined');
break;
case '':
console.log(prop + ' is empty string');
break;
case 0:
console.log(prop + ' is 0');
break;
default:
}
}
};
You can validate the fields array if you please
Array.isArray(fields)
Upvotes: 0
Reputation: 173
Usage
exempleUsage(username: string, password: string) {
checkRequiredProperties({username, password})
return this.apiService.post(`/.../...`)
}
Helpers
const isEmpty = (v) => (!v && v !== false && v !== 0);
const handleErrorForCB = (error, errorCB) => {
if (!errorCB) throw error;
errorCB(error);
};
export function hasProperty(o, p) {
return !isEmpty(_.get(o, p));
}
export function checkRequiredProperties(o, properties = Object.keys(o), errorCB) {
const errors = properties.filter(p => !hasProperty(o, p));
if (errors.length > 0) handleErrorForCB(new Error("Missing properties: " + errors), errorCB);
}
export function checkFoundProperties(o, properties = Object.keys(o), errorCB) {
const errors = properties.filter(p => !hasProperty(o, p));
if (errors.length > 0) handleErrorForCB(new Error("Not found: " + errors), errorCB);
}
Upvotes: 0
Reputation: 41
Here's a function that will check that an Object contains every required key by comparing its keys to an array of strings. Since it validates by comparing lengths, it won't matter if the order is different or if the object contains additional information.
const hasRequiredKeys = (object, requiredKeys) => {
return Object.keys(object).filter(key => requiredKeys.includes(key)).length === requiredKeys.length
}
Upvotes: 0
Reputation: 9214
You can use https://github.com/jquense/yup to solve your problem.
import * as yup from 'yup';
let schema = yup.object().shape({
name: yup.string().required(),
age: yup.number().required().positive().integer(),
address: yup.string().required(),
});
var info = {
name: "John Doe",
age: "",
phone: "123-456-7890"
}
// check validity
schema
.isValid(info)
.then(function (valid) {
valid; // => false
});
This is doing a bit more than just checking for the existence of fields, it's also ensuring that the data type is correct e.g. that age is a non-negative number.
Upvotes: 13
Reputation: 51916
If you want "elegant", what you're looking for is called a schema
:
var schema = {
name: function (value) {
return /^([A-Z][a-z\-]* )+[A-Z][a-z\-]*( \w+\.?)?$/.test(value);
},
age: function (value) {
return !isNaN(value) && parseInt(value) == value && value >= 18;
},
phone: function (value) {
return /^(\+?\d{1,2}-)?\d{3}-\d{3}-\d{4}$/.test(value);
}
};
var info = {
name: "John Doe",
age: "",
phone: "123-456-7890"
};
function validate(object, schema) {
var errors = Object.keys(schema).filter(function (key) {
return !schema[key](object[key]);
}).map(function (key) {
return new Error(key + " is invalid.");
});
if (errors.length > 0) {
errors.forEach(function (error) {
console.log(error.message);
});
} else {
console.log("info is valid");
}
}
validate(info, schema);
To address @AndreFigueiredo's pedantry, you can also check if the object
contains the property at all:
var schema = {
name: function (value) {
return /^([A-Z][a-z\-]* )+[A-Z][a-z\-]*( \w+\.?)?$/.test(value);
},
age: function (value) {
return !isNaN(value) && parseInt(value) == value && value >= 18;
},
phone: function (value) {
return /^(\+?\d{1,2}-)?\d{3}-\d{3}-\d{4}$/.test(value);
}
};
schema.name.required = true;
schema.age.required = true;
var info = {
// name: "John Doe",
age: "",
phone: "123-456-7890"
};
function validate(object, schema) {
var errors = Object.keys(schema).map(function (property) {
var validator = schema[property];
return [property, !validator.required || (property in object), validator(object[property])];
}).filter(function (entry) {
return !entry[1] || !entry[2];
}).map(function (entry) {
if (!entry[1]) return new Error(entry[0] + " is required.");
else return new Error(entry[0] + " is invalid.");
});
if (errors.length > 0) {
errors.forEach(function (error) {
console.log(error.message);
});
} else {
console.log("info is valid");
}
}
validate(info, schema);
Here's a modernized solution using features from ECMAScript 6 edition including destructuring, arrow functions, Object.entries()
, template literals, and for...of
:
const schema = {
name: value => /^([A-Z][a-z\-]* )+[A-Z][a-z\-]*( \w+\.?)?$/.test(value),
age: value => parseInt(value) === Number(value) && value >= 18,
phone: value => /^(\+?\d{1,2}-)?\d{3}-\d{3}-\d{4}$/.test(value)
};
let info = {
name: 'John Doe',
age: '',
phone: '123-456-7890'
};
const validate = (object, schema) => Object
.keys(schema)
.filter(key => !schema[key](object[key]))
.map(key => new Error(`${key} is invalid.`));
const errors = validate(info, schema);
if (errors.length > 0) {
for (const { message } of errors) {
console.log(message);
}
} else {
console.log('info is valid');
}
And the version that performs required
and validate
checks separately:
const schema = {
name: value => /^([A-Z][a-z\-]* )+[A-Z][a-z\-]*( \w+\.?)?$/.test(value),
age: value => parseInt(value) === Number(value) && value >= 18,
phone: value => /^(\+?\d{1,2}-)?\d{3}-\d{3}-\d{4}$/.test(value)
};
schema.name.required = true;
schema.age.required = true;
let info = {
// name: 'John Doe',
age: '',
phone: '123-456-7890'
};
const validate = (object, schema) => Object
.entries(schema)
.map(([key, validate]) => [
key,
!validate.required || (key in object),
validate(object[key])
])
.filter(([_, ...tests]) => !tests.every(Boolean))
.map(([key, invalid]) => new Error(`${key} is ${invalid ? 'invalid' : 'required'}.`));
const errors = validate(info, schema);
if (errors.length > 0) {
for (const { message } of errors) {
console.log(message);
}
} else {
console.log('info is valid');
}
Upvotes: 45
Reputation: 386680
I would do for a real check for empty strings, because 0
is falsy, but a value and not empty.
function validateFields(object, keys) {
keys.forEach(function (k) {
if (k in object) {
console.log(k + ": " + object[k]);
if (object[k] === '') {
console.log(k + " exists but is empty");
}
return;
}
console.log(k + " doesn't exist in object");
});
}
var fields = ['name', 'age', 'address', 'zeroString', 'zeroNumber'],
info = { name: "John Doe", age: "", phone: "123-456-7890", zeroString: '0', zeroNumber: 0 };
validateFields(info, fields);
Upvotes: 1
Reputation: 4203
I think that you should be more concerned about what's the proper way of checking for it, rather than the most elegant. I'll drop a link to a very good post from Todd Moto regarding this: Please, take a look
In short your code should look like this:
var validateFields = function(o, required_fields) {
required_fields.forEach(function(field){
if(field in o){
if((typeof o[field] != 'undefined')){
console.log(field + ": " + o[field]);
}else{
console.log(field + " exists but is undefined");
}
}else{
console.log(field + " doesn't exist in object");
}
});
}
Note: Take care when checking it has value, many expressions in javasscript are falsy (eg. 0, false, etc.) but are valid values.
Upvotes: 2