Aleksandar Terziev
Aleksandar Terziev

Reputation: 2558

How to validate deeply nested object structure

I have defined object with nested properties. I want to create a validator function which will check if another object has the same structure and value type as the one that I have defined!

The is the definition of the object:

const OBJECT_SCHEMA = {
  name: String,
  data: [{
    isSelected: Boolean,
    mId: String,
    mSummary: String,
    mMarkets: Array,
    mBdd: String,
    mReplaceDict: Object,
    omId: String,
    omnSummary: String,
    omnMarkets: Array,
    omnBdd: String,
    omnReplaceDict: {
      id: String,
      text: String,
    },
  }],
  metadata: {
    emails: Array,
    description: String,
  },
};

And here is the function that I have for validation. Currently it works only with one nested level! I want it to validate with many nested levels.

function validateObjectStructure(schema, obj) {
  let valid = true;
  firstLevel: for(const k in schema) {
    if(schema[k].constructor === Array) { // if prop is of type array
      let i;
      for(i = 0; i < schema[k].length; i++) {
        for(const kk in schema[k][i]) {
          if(!obj[k][i].hasOwnProperty(kk) || obj[k][i][kk].constructor !== schema[k][i][kk]) {
            valid = false;
            break firstLevel;
          }
        }
      }
    }
    else if(schema[k].constructor === Object) { // if prop is of type object
      for(const kk in schema[k]) {
        if(!obj[k].hasOwnProperty(kk) || obj[k][kk].constructor !== schema[k][kk]) {
          valid = false;
          break firstLevel;
        }
      }
    }
    else { // if prop is simple type
      if(!obj.hasOwnProperty(k) || obj[k].constructor !== schema[k]) {
        valid = false;
        break;
      }
    }
  }
  return valid;
}

Upvotes: 2

Views: 3757

Answers (2)

georg
georg

Reputation: 215009

Here's a possible implementation:

function validate(obj, schema, path = '') {
    let ok = true;

    if (!obj)
        ok = obj === schema;
    else if (typeof schema === 'function')
        ok = obj.constructor === schema;
    else if (typeof obj !== 'object')
        ok = obj === schema;
    else if (Array.isArray(schema))
        ok = Array.isArray(obj) && obj.every((x, k) => validate(x, schema[0], path + '[' + k + ']'));
    else {
        let ko = Object.keys(obj);
        let ks = Object.keys(schema);
        ok = ko.length === ks.length && ks.every(k => validate(obj[k], schema[k], path + '.' + k));
    }
    if (!ok)
        throw new Error('FAILED ' + path);
    return true;
}

// example:


const OBJECT_SCHEMA = {
    name: String,
    data: [{
        isSelected: Boolean,
        mId: String,
        omnReplaceDict: {
            id: String,
            text: {
                deepObj: {
                    deepProp: [Number]
                }

            },
        },
    }],
};

const obj = {
    name: "foo",
    data: [{
        isSelected: true,
        mId: "bar",
        omnReplaceDict: {
            id: "foo",
            text: {
                deepObj: {
                    deepProp: [1, 2, "???", 3]
                }

            },
        },
    }]
};


validate(obj, OBJECT_SCHEMA)

Note: although this home-made type checker appears to work correctly, it's quite limited (e.g. how to express "array of string-number pairs" or "either null or some object"?), so it might be an option to employ a real one, like Typescript. See here for a possible implementation.

Upvotes: 1

viktarpunko
viktarpunko

Reputation: 353

Do you need to work with nested levels of the obj? If yes, you can do something like this instead of the last line:

Object.values(obj).reduce((accValid, value) => {
  if (typeof value === 'object') {
    return accValid && validateObjectStructure(schema, value);
  }
  return accValid;
}, valid);

return valid;

Upvotes: 1

Related Questions