Skip to content Skip to sidebar Skip to footer

Remove Empty Objects From An Object Using Recursion (using Vanila Es6 Javascript)

I am trying to remove null & empty objects inside an object (but not falsy values), with recursively. I have implemented logic without recursion not sure how to do with recursi

Solution 1:

It's an interesting problem. I think it can be solved elegantly if we write a generic map and filter function that works on both Arrays and Objects -

functionmap (t, f)
{ switch (t?.constructor)
  { caseArray:
      return t.map(f)
    caseObject:
      returnObject.fromEntries(Object.entries(t).map(([k, v]) =>  [k, f(v, k)]))
    default:
      return t
  }
}

functionfilter (t, f)
{ switch (t?.constructor)
  { caseArray:
      return t.filter(f)
    caseObject:
      returnObject.fromEntries(Object.entries(t).filter(([k, v]) =>f(v, k)))
    default:
      return t
  }
}

We can write your removeEmpties program easily now -

const empty =
  Symbol("empty") // <- sentinelfunctionremoveEmpties (t)
{ switch (t?.constructor)
  { caseArray:
    caseObject:
      returnfilter(map(t, removeEmpties), nonEmpty)
    default:
      returnnonEmpty(t) ? t : empty  // <-
  }
}

Now we have to define what it means to be nonEmpty -

functionnonEmpty (t)
{ switch (t?.constructor)
  { caseArray:
      return t.length > 0caseObject:
      returnObject.keys(t).length > 0default:
      return t !== empty // <- all other t are OK, except for sentinel
  }
}

Finally we can compute the result of your input -

const input =
    {a: {b: 1, c: {a: 1, d: {}, e: {f: {}}}}, b: {}}

console.log(removeEmpties(input))

Expand the snippet below to verify the result in your browser -

const empty =
  Symbol("empty")

functionremoveEmpties (t)
{ switch (t?.constructor)
  { caseArray:
    caseObject:
      returnfilter(map(t, removeEmpties), nonEmpty)
    default:
      returnnonEmpty(t) ? t : empty
  }
}

functionnonEmpty (t)
{ switch (t?.constructor)
  { caseArray:
      return t.length > 0caseObject:
      returnObject.keys(t).length > 0//case String:             // <- define any empty types you want//  return t.length > 0default:
      return t !== empty // <- all other t are OK, except for sentinel
  }
}

functionmap (t, f)
{ switch (t?.constructor)
  { caseArray:
      return t.map(f)
    caseObject:
      returnObject.fromEntries(Object.entries(t).map(([k, v]) =>  [k, f(v, k)]))
    default:
      return t
  }
}

functionfilter (t, f)
{ switch (t?.constructor)
  { caseArray:
      return t.filter(f)
    caseObject:
      returnObject.fromEntries(Object.entries(t).filter(([k, v]) =>f(v, k)))
    default:
      return t
  }
}

const input =
  {a: {b: 1, c: {a: 1, d: {}, e: {f: {}}}}, b: {}}

console.log(removeEmpties(input))
{
  "a": {
    "b": 1,
    "c": {
      "a": 1
    }
  }
}

If you don't want to include null or undefined -

functionnonEmpty (t)
{ switch (t?.constructor)
  { caseArray:
      // ...caseObject:
      // ...default:
      return t != null && !== empty // <- 
  }
}

If you don't want to include empty strings, numbers, or false booleans -

functionnonEmpty (t)
{ switch (t?.constructor)
  { caseArray:
      // ...caseObject:
      // ...caseString:
      return t.length > 0// <-caseNumber:
      return t != 0// <-caseBoolean:
      return !t            // <-default:
      // ...
  }
}

See this Q&A for a related approach and explanation of inductive reasoning.

Solution 2:

Something like this should do it.

constisNullOrEmpty = (obj) =>
  obj == null || (Object (obj) === obj && Object .keys (obj) .length == 0)

constclearNullEmpties = (obj) => 
  Object (obj) === obj
    ? Object .fromEntries (Object .entries (obj) .flatMap (([k, v]) => {
        const val = clearNullEmpties (v) 
        return isNullOrEmpty (val) ? [] : [[k, val]]
      }))
    : obj
 
const object = {a: {b: 1, c: {a: 1, d: {}, e: {f: {}}}}, b: {}}

console .log (clearNullEmpties (object))

We use Object.entries to break our object into key-value pairs, and then using flatMap, we combine a filtering and mapping operation on those pairs, recursively running the values through this function and keeping those ones that do not return an empty value, using our helper isNullOrEmpty. Then we recombine the results with Object.fromEntries.

If you need to handle arrays as well, that would take a little additional work, but shouldn't be hard.

Note that the above is a little more beginner-friendly than my personal preferred variant of this, which uses only expressions and no statements, and involves one more helper function:

constcall = (fn, ...args)=>
  fn (...args)

constclearNullEmpties = (obj) => 
  Object (obj) === obj
    ? Object .fromEntries (Object .entries (obj) .flatMap (([k, v]) => call (
        (val = clearNullEmpties (v)) => !isNullOrEmpty (val) ? [[k, val]] : []
      )))
    : obj

Solution 3:

Try like this:

constclearNullEmpties = (obj) => {
  let newObj = {}

  Object.entries(obj).forEach(([k, val], i) => {
    const isNotNull = val !== nullconst isObject = isNotNull && val.constructor === Objectconst isEmptyObj = isObject && Object.keys(val).length === 0/* call twice in case the result returned is iteslf an empty object,
    as with e in the Example Data */const result = (isObject && !isEmptyObj && clearNullEmpties(clearNullEmpties(val))) 
    
    if(isObject) {
      result && Object.assign(newObj, { [k]: result })
    } elseif (isNotNull) {
      Object.assign(newObj, { [k]: (val) })
    }
  })

  return newObj
}


// Example Datalet object = {
    a: {
        b: 1,
        c: {
            a: 1,
            d: null,
            e: {
              f: {} 
            }
        }
    },
    b: {}
}


let expectedResult = {
    a: {
        b: 1,
        c: {
            a: 1,
        }
    }
}

console.log("result", clearNullEmpties(object))
console.log("expected", expectedResult)

Post a Comment for "Remove Empty Objects From An Object Using Recursion (using Vanila Es6 Javascript)"