Search code examples
javascripttypescriptrecursionpojo

Check all properties are not null in n-depth POJO


I am needing a function that takes any object and returns true/false if every child meets a condition. Currently that condition is that the property is truthy. So null and undefined should return false, but a value of false is a pass.

The project I'm working in is in Typescript, but this applies to vanilla JS as well, and I am also able to use lodash.

I've started with this function so far:

function CheckAllPropertiesDefined(obj){
  if (!obj) {
    return false;
  }

  for (const key in obj) {
    if (o[key] === null || o[key] === undefined) {
      return false;
    }
  }

  return true;
}

However the requirements of what I'm doing changed (no longer a flat object) and I realised it's not working for nested properties.

The outcome of running it against this object is true, when it shouldn't be:

const obj = {
  prop1: true,
  prop2: true,
  prop3: {
    prop3a: false,
  },
  prop4: {
    test: null,
    foo: false,
    child: {
      child2: null,
    },
  },
};

It should return false because prop4.test and prop4.child.child2 are null. I am figuring some recursion is necessary.


Solution

  • I generally prefer to split apart the traversal of the object from the action we perform on it. Here, I would write a generic function that can apply a predicate against any (scalar) node, and recurses on objects or arrays. Then we can simply call it with a function that tests that the node is not null or undefined to get back a function to use in testing an object. This is an example:

    const checkAllDeep = (pred) => (xs) =>
      Array .isArray (xs)
        ? xs .every (x => checkAllDeep (pred) (x))
      : xs && typeof xs == 'object'
        ? Object .entries (xs) .every (([k, v]) => checkAllDeep (pred) (v))
      : pred (xs)
    
    const checkAllPropertiesDefined = checkAllDeep (x => x !== null && x !== undefined)
    
    const obj = {prop1: true, prop2: true, prop3: {prop3a: false}, prop4: {test: null, foo: false, child: {child2: null}}}
    
    console .log (checkAllPropertiesDefined (obj))

    Later if we want to change the predicate (we want to accept null but not undefined, for instance), it's easy to find. And we could write other functions such as const allPropsAreStrings (checkAllDeep ((x) => typeof x == 'string') quite simply.