implement a function which adds a type validation to an object

Viewed 2234

Your function should receive an object as its only argument and return an object with the same properties, but with type validation added. Types should be validated when:

  • the function creates the object;
  • Someone updates a property;
  • Someone adds a property;

The type validation should always be based on the last part of the property name. For example, age_int property should always be an integer and throw an error when set to something else

Here are possible types:

  • string: for example, "string type"
  • int: 12.00 and 12 are both integers.
  • float: for example, 12.34
  • number: any int or float
  • bool: for example, true

Assumptions

  • Types are optional and validation should be skipped if the type isn't specified.
  • always precedes the type name.

Examples

Your function should behave as shown below:

const obj= {
age_int: 2,
name_string:"John",
Job: null,
}

const validatingbject=typeCheck(obj)

validatingobject.age_int=2.25 // Throws error
validatingbject.age.int= 2
validatingoject.job="fireman"
validatingbject.address_string= 20 // Throws error


const obj_2= {employed_bool: "true",}

const validatingobject = typeCheck(obj_2) // Throws error

I tried the code below but was unsuccessful.

function typeCheck(object) {

  console.log(Object.entries(object));

  Object.entries(object).forEach(([key, value]) => {
    let type = key.split('_').pop();
    let typecheck;
    console.log("type:", type);
    if (type === "float" || type === "int" || type === "number") {
      typecheck = "number";
    } else if (type === "bool") {
      typecheck = "boolean";
    } else if (type === "string") {
      typecheck = "string";
    }
    if (typeof value == typecheck) {
      return true;
    } else {
      console.error("error")
    }

  });

}

const obj = {
  age_int: 2,
  name_string: "John",
  Job: null,
}

const validatingbject = typeCheck(obj);

6 Answers
function checkingInitialType(object) {
   for (key in object) {
       if(key.indexOf('_') !== -1) {
    if (key.includes('string')) {
      console.log(object[key]);
      if (typeof(object[key]) != 'string') {
        throw Error;
      }
    } else if (key.includes('int') || key.includes('float') || key.includes('number')) {
      console.log(object[key]);
      if (typeof(object[key]) != 'number') {
        throw Error;
      } else if (key.includes('int')) {
        if (Number.isInteger(object[key])) {
          console.log("true");
        } else {
          throw Error;
        }
      } else if (key.includes('float')) {
        if (Number.isInteger(object[key])) {
          throw Error;
        } else {
          console.log("true");
        }
      }
    } else if (key.includes('bool')) {
      if (typeof(object[key]) != 'boolean') {
        throw Error;
      }
    } else if (typeof(object[key] == 'object')) {
      console.log('object' + " " + object[key]);
    } else {
      return object;
    }
       } else {
           return object;
       }
  }
  return object;
}

const obj = {
    age_int: 2,
    name_string: "Adam",
    job: null,
}

const handler2 = {
    set(target, property, value) {
        if(property.indexOf('_') !== -1) {
        if (property.indexOf('string') !== -1) {
          if (typeof(value) != 'string') {
            throw Error;
          }
        } else if (property.indexOf('int')  !== -1 || property.indexOf('float')  !== -1 || property.indexOf('number')  !== -1) {
          if (typeof(value) != 'number') {
            throw Error;
          } else if (property.indexOf('int')  !== -1) {
            if (Number.isInteger(value)) {
              console.log("true");
            } else {
              throw Error;
            }
          } else if (property.indexOf('float')  !== -1) {
            if (Number.isInteger(value)) {
              throw Error;
            } else {
              console.log("true");
            }
          }
        } else if (property.indexOf('bool')  !== -1) {
          if (typeof(value) != 'boolean') {
            throw Error;
          }
        }
        return target[property] = value
        } else {
            return target[property] = value
        }
    
    }
}

function typeCheck(obj) {
    const initialObj = checkingInitialType(obj)
    const proxy =  new Proxy(initialObj, handler2);
    return proxy;
}

module.exports = typeCheck;

You need to use the proxy api in javascript

typeof value == typecheck, should have parenthesis, to make it clear how it behave:

typeof "string" == 33 // false
typeof ("string" == 33) // 'boolean'
(typeof "string") == 33 // false

Be careful on your split, it doesn't error if no underscore is present:

"age".split('_').pop(); // "age"
"age_int".split('_').pop(); // "int"
"int".split('_').pop(); // "int" <-- this may throw you off

Check that you have an _ underscore with a valid type ("int", "string", any of your custom types) for each key. If not, you skip the iteration.

Maybe you want to use throw new Error("message"), if you want to stop program execution.

Your code does seem to work as you expect it to, if you fix the split issue.

Just in case, maybe you are looking for Typescript, class-validator, or yup (or any equivalent).


Edit: I see you also want an object to be returned const validatingbject=typeCheck(obj), but typecheck function contain no return statement.

If you want validatingobject.age_int=2.25 to error, you first need to have a setter ... here you go this should help you:

function validateTypeOrThrow(e, type) {
  if (type === 'int' && !Number.isInteger(e)) {
    throw new Error(`${e} is not a ${type} !`);
  }
}

const obj = {};

obj._age_int = 33; // initial value

// _age_int starting with underscore is convention for private
Object.defineProperty(obj, 'age_int', {
  set(value) {
    // this will infinite loop
    // this.age_int = value;

    this._age_int = value;
  },
  get () {
    // this will infinite loop
    // return this.age_int;

    validateTypeOrThrow(this._age_int , 'int');

    return this._age_int;
  },
});

console.log(obj.age_int);

obj.age_int = 22;
console.log(obj.age_int)

obj.age_int = 'str'; // no error
console.log(obj.age_int) // error

Here is the solution Using includes()

function typeCheck(object) {
  for (key in object) {

    if (key.includes('string')) {
      console.log(object[key]);
      if (typeof(object[key]) != 'string') {
        throw Error;
      }
    } else if (key.includes('int') || key.includes('float') || key.includes('number')) {
      console.log(object[key]);
      if (typeof(object[key]) != 'number') {
        throw Error;
      } else if (key.includes('int')) {
        if (Number.isInteger(object[key])) {
          console.log("true");
        } else {
          throw Error;
        }
      } else if (key.includes('float')) {
        if (Number.isInteger(object[key])) {
          throw Error;
        } else {
          console.log("true");
        }
      }
    } else if (key.includes('bool')) {
      if (typeof(object[key]) != 'boolean') {
        throw Error;
      }
    } else if (typeof(object[key] == 'object')) {
      console.log('object' + " " + object[key]);
    } else {
      return object;
    }
  }

  return object;
}

const obj = {
  age_int: 23,
  name_string: "name",
  age_float: 22.4,
  Job: null,
}

const validatingbject = typeCheck(obj);
console.log(validatingbject);
validatingbject.job = "fireman";
validatingbject.age_int = 2.25;
typeCheck(obj);

const obj_2 = {
  employed_bool: "true",
}
validatingbject = typeCheck(obj_2)

function isInt(n) {
  return Number(n) === n && n % 1 === 0;
}

function isFloat(n) {
  return Number(n) === n && n % 1 !== 0;
}
function isString(s) {
  return typeof s === "string";
}
function isBoolean(val) {
  return "boolean" === typeof val;
}

function isNum(n) {
  return typeof n === "number";
}

function checktype(key, value, typeMap) {
  if (typeMap[key]) {
    if (typeMap[key] === "string") {
      if (!isString(value)) throw new Error("String Error");
    }
    if (typeMap[key] === "number") {
      if (!isNum(value)) throw new Error("Num Error");
    }
    if (typeMap[key] === "float") {
      if (!isFloat(value)) throw new Error("Float Error");
    }
    if (typeMap[key] === "int") {
      if (!isInt(value)) throw new Error("Int Error");
    }
    if (typeMap[key] === "bool") {
      if (!isBoolean(value)) throw new Error("Boolean Error");
    }
  }
}

function typeCheck(obj) {
  var result = {};
  var keys = {};
  Object.keys(obj).forEach((o) => {
    let val = o.split("_");
    val = val[val.length - 1];
    keys[o] = val;
  });

  for (const key in obj) {
    var value = obj[key];
    Object.defineProperty(result, key, {
      enumerable: true,
      configurable: true,
      set(nv) {
        checktype(key, nv, keys);
        value = nv;
      },
      get() {
        return value;
      },
    });
  }
  return result;
}

Any update if need typeCheck to validate number, bool, string on property creation and updation.

function typeCheck(object) {
  for (key in object) {

    if (key.includes('string')) {
      console.log(object[key]);
      if (typeof(object[key]) != 'string') {
        throw Error;
      }
    } else if (key.includes('int') || key.includes('float') || key.includes('number')) {
      console.log(object[key]);
      if (typeof(object[key]) != 'number') {
        throw Error;
      } else if (key.includes('int')) {
        if (Number.isInteger(object[key])) {
          console.log("true");
        } else {
          throw Error;
        }
      } else if (key.includes('float')) {
        if (Number.isInteger(object[key])) {
          throw Error;
        } else {
          console.log("true");
        }
      }
    } else if (key.includes('bool')) {
      if (typeof(object[key]) != 'boolean') {
        throw Error;
      }
    } else if (typeof(object[key] == 'object')) {
      console.log('object' + " " + object[key]);
    } else {
      return object;
    }
  }

  return object;
}

const obj = {
  age_int: 23,
  name_string: "name",
  age_float: 22.4,
  Job: null,
}

const validatingbject = typeCheck(obj);
console.log(validatingbject);
validatingbject.job = "fireman";
validatingbject.age_int = 2.25;
typeCheck(obj);

const obj_2 = {
  employed_bool: "true",
}
validatingbject = typeCheck(obj_2)

We need to handle with Proxy to do validation after applying the method on an object.

function checkType(dataType, value) {
    if (dataType === 'int') {
        return Number(value) === value && value % 1 === 0;
    }
    if (dataType === 'float') {
        return Number(value) === value && value % 1 !== 0;
    }
    if (dataType === 'number') {
        return typeof value === 'number';
    }
    if (dataType === 'string') {
        return typeof value === 'string';
    }
    if (dataType === 'bool') {
        return typeof value === 'boolean';
    }
    return true;

}

function validator(object) {
    const newObject = {};
    for (const field of Object.keys(object)) {
        const fieldArr = field.split('_');
        const dataType = fieldArr[1] || '';
        
        if (!checkType(dataType, object[field])) {
            throw new Error(`Invalid data type for field ${field}`);
        }

        newObject[field] = object[field];
    }
    return newObject;
}

function typeCheck(object) {
    const newObject = validator(object)
    const proxy =  new Proxy(newObject, {
        set: (target, prop, value)=>{
            const fieldArr = prop.split('_');
            const dataType = fieldArr[1] || '';
            if (!checkType(dataType, value)) {
                throw new Error(`Invalid data type for field ${prop}`);
            }
            target[prop] = value;
        }
    });
    return proxy;
}

const obj = {
  age_int: 2,
  name_string:"John",
  job: null,
}

const validatingobject = typeCheck(obj)

validatingobject.age_int = 2.25 // Throws error
validatingobject.age_int = 2
validatingobject.job = "fireman"
validatingobject.address_string = 20 // Throws error

Related