Skip to content Skip to sidebar Skip to footer

Remove All Empty Strings, Empty Objects, And Empty Arrays From Js Object

this is my object: { 'name':'fff', 'onlineConsultation':false, 'image':'', 'primaryLocation':{ 'locationName':'ggg', 'street':'', }, 'billin

Solution 1:

One nice thing about keeping around helpful functions is that you can often solve for your new requirements pretty simply. Using some library functions I've written over the years, I was able to write this version:

constremoveEmpties = (input) =>
  pathEntries (input)
    .filter (([k, v]) => v !== '')
    .reduce ((a, [k, v]) => assocPath (k, v, a), {})

This uses two function I had around, pathEntries and assocPath, and I'll give their implementations below. It returns the following when given the input you supplied:

{
    name: "fff",
    onlineConsultation: false,
    primaryLocation: {
        locationName: "ggg"
    },
    education: [
        {
            nameOfInstitution: "ffff",
            description: "fff"
        }
    ]
}

This removes empty string, arrays with no values (after the empty strings are removed) and objects with no non-empty values.

We begin by calling pathEntries (which I've used in other answers here, including a fairly recent one.) This collects paths to all the leaf nodes in the input object, along with the values at those leaves. The paths are stored as arrays of strings (for objects) or numbers (for arrays.) And they are embedded in an array with the value. So after that step we get something like

[
  [["name"], "fff"],
  [["onlineConsultation"], false],
  [["image"], ""],
  [["primaryLocation", "locationName"], "ggg"],
  [["primaryLocation", "street"], ""],
  [["categories", 0], ""], 
  [["concernsTreated", 0], ""], 
  [["education", 0, "nameOfInstitution"], "ffff"],
  [["education", 0, "description"],"fff"],
  [["experience", 0, "from"], ""],
  [["experience", 0, "current"], ""]
]

This should looks something like the result of Object.entries for an object, except that the key is not a property name but an entire path.

Next we filter to remove any with an empty string value, yielding:

[
  [["name"], "fff"],
  [["onlineConsultation"], false],
  [["primaryLocation", "locationName"], "ggg"],
  [["education", 0, "nameOfInstitution"], "ffff"],
  [["education", 0, "description"],"fff"],
]

Then by reducing calls to assocPath (another function I've used quite a few times, including in a very interesting question) over this list and an empty object, we hydrate a complete object with just these leaf nodes at their correct paths, and we get the answer we're seeking. assocPath is an extension of another function assoc, which immutably associates a property name with a value in an object. While it's not as simple as this, due to handling of arrays as well as objects, you can think of assoc like (name, val, obj) => ({...obj, [name]: val})assocPath does something similar for object paths instead of property names.

The point is that I wrote only one new function for this, and otherwise used things I had around.

Often I would prefer to write a recursive function for this, and I did so recently for a similar problem. But that wasn't easily extensible to this issue, where, if I understand correctly, we want to exclude an empty string in an array, and then, if that array itself is now empty, to also exclude it. This technique makes that straightforward. In the implementation below we'll see that pathEntries depends upon a recursive function, and assocPath is itself recursive, so I guess there's still recursion going on!

I also should note that assocPath and the path function used in pathEntries are inspired by Ramda (disclaimer: I'm one of it's authors.) I built my first pass at this in the Ramda REPL and only after it was working did I port it to vanilla JS, using the versions of dependencies I've created for those previous questions. So even though there are a number of functions in the snippet below, it was quite quick to write.

constpath = (ps = []) => (obj = {}) =>
  ps .reduce ((o, p) => (o || {}) [p], obj)

constassoc = (prop, val, obj) => 
  Number .isInteger (prop) && Array .isArray (obj)
    ? [... obj .slice (0, prop), val, ...obj .slice (prop + 1)]
    : {...obj, [prop]: val}

constassocPath = ([p = undefined, ...ps], val, obj) => 
  p == undefined
    ? obj
    : ps.length == 0
      ? assoc(p, val, obj)
      : assoc(p, assocPath(ps, val, obj[p] || (obj[p] = Number .isInteger (ps[0]) ? [] : {})), obj)

constgetPaths = (obj) =>
  Object (obj) === obj
    ? Object .entries (obj) .flatMap (
        ([k, v]) => getPaths (v) .map (p => [Array.isArray(obj) ? Number(k) : k, ... p])
      )
    : [[]]

constpathEntries = (obj) => 
  getPaths (obj) .map (p => [p, path (p) (obj)])

constremoveEmpties = (input) =>
  pathEntries (input)
    .filter (([k, v]) => v !== '')
    .reduce ((a, [k, v]) => assocPath (k, v, a), {})

const input = {name: "fff", onlineConsultation: false, image: "", primaryLocation: {locationName: "ggg", street:""}, billingAndInsurance: [], categories: [""], concernsTreated: [""], education: [{nameOfInstitution: "ffff", description: "fff"}], experience: [{from: "", current:""}]}

console .log(removeEmpties (input))

At some point, I may choose to go a little further. I see a hydrate function looking to be pulled out:

consthydrate = (entries) =>
  entries .reduce ((a, [k, v]) =>assocPath2(k, v, a), {})

constremoveEmpties = (input) =>
  hydrate (pathEntries (input) .filter (([k, v]) => v !== ''))

And I can also see this being written more Ramda-style like this:

const hydrate = reduce ((a, [k, v]) =>assocPath(k, v, a), {})

const removeEmpties = pipe (pathEntries, filter(valueNotEmpty), hydrate)

with an appropriate version of valuesNotEmpty.

But all that is for another day.

Solution 2:

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 -

constmap = (t, f) =>
  isArray(t)
    ? t.map(f)
: isObject(t)
    ? Object.fromEntries(Object.entries(t).map(([k, v]) =>  [k, f(v, k)]))
: t

constfilter = (t, f) =>
  isArray(t)
    ? t.filter(f)
: isObject(t)
    ? Object.fromEntries(Object.entries(t).filter(([k, v]) =>f(v, k)))
: t

We can write your removeEmpties program easily now -

  1. if the input, t, is an object, recursively map over it and keep the non-empty values
  2. (inductive) t is not an object. If t is a non-empty value, return t
  3. (inductive) t is not an object and t is an empty value. Return the empty sentinel
const empty =
  Symbol()

constremoveEmpties = (t = {}) =>
  isObject(t)
    ? filter(map(t, removeEmpties), nonEmpty) // 1
: nonEmpty(t)
    ? t                                       // 2
: empty                                       // 3

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

const nonEmpty = t =>
  isArray(t)
    ? t.length > 0
: isObject(t)
    ? Object.keys(t).length > 0
: isString(t)
    ? t.length > 0
: t !== empty// <- all other t are OK, except for sentinel

To this point we have use is* functions to do dynamic type-checking. We will define those now -

constisArray = t => Array.isArray(t)
constisObject = t => Object(t) === t
constisString = t => String(t) === t
constisNumber = t => Number(t) === t
constisMyType = t => // As many types as you want 

Finally we can compute the result of your input -

const input =
  {name:"fff",zero:0,onlineConsultation:false,image:"",primaryLocation:{locationName:"ggg",street:""},billingAndInsurance:[],categories:[""],concernsTreated:[""],education:[{nameOfInstitution:"ffff",description:"fff"}],experience:[{from:"",current:""}]}

const result =
  removeEmpties(input)

console.log(JSON.stringify(result, null, 2))
{"name":"fff","zero":0,"onlineConsultation":false,"primaryLocation":{"locationName":"ggg"},"education":[{"nameOfInstitution":"ffff","description":"fff"}]}

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

constmap = (t, f) =>
  isArray(t)
    ? t.map(f)
: isObject(t)
    ? Object.fromEntries(Object.entries(t).map(([k, v]) =>  [k, f(v, k)]))
: t

constfilter = (t, f) =>
  isArray(t)
    ? t.filter(f)
: isObject(t)
    ? Object.fromEntries(Object.entries(t).filter(([k, v]) =>f(v, k)))
: t

const empty =
  Symbol()

constremoveEmpties = (t = {}) =>
  isObject(t)
    ? filter(map(t, removeEmpties), nonEmpty)
: nonEmpty(t)
    ? t
: empty

constisArray = t => Array.isArray(t)
constisObject = t => Object(t) === t
constisString = t => String(t) === t

constnonEmpty = t =>
  isArray(t)
    ? t.length > 0
: isObject(t)
    ? Object.keys(t).length > 0
: isString(t)
    ? t.length > 0
: t !== empty

const input =
  {name:"fff",zero:0,onlineConsultation:false,image:"",primaryLocation:{locationName:"ggg",street:""},billingAndInsurance:[],categories:[""],concernsTreated:[""],education:[{nameOfInstitution:"ffff",description:"fff"}],experience:[{from:"",current:""}]}

const result =
  removeEmpties(input)

console.log(JSON.stringify(result, null, 2))

Solution 3:

functionremoveEmpty(obj){
    if(obj.__proto__.constructor.name==="Array"){
            obj = obj.filter(e=>e.length)
            return obj.map((ele,i)=>{
            if(obj.__proto__.constructor.name==="Object")returnremoveEmpty(ele) /* calling the same function*/elsereturn ele
        })
    }

   if(obj.__proto__.constructor.name==="Object")for(let key in obj){
        switch(obj[key].__proto__.constructor.name){
            case"String":
                            if(obj[key].length===0)delete obj[key]
                            break;
            case"Array":
                            obj[key] = removeEmpty(obj[key]) /* calling the same function*/if(! obj[key].length)delete obj[key]
                            break;
            case"Object":
                            obj[key] = removeEmpty(obj[key]) /* calling the same function*/break;
        }
    }
    return obj;
}

const input = {name: "fff", onlineConsultation: false, image: "", primaryLocation: {locationName: "ggg", street:""}, billingAndInsurance: [], categories: [""], concernsTreated: [""], education: [{nameOfInstitution: "ffff", description: "fff"}], experience: [{from: "", current:""}]}

console .log(removeEmpty(input))

Solution 4:

functiondropEmptyElements(object) {
    switch (typeof object) {
        case"object":
            const keys = Object.keys(object || {}).filter(key => {
                const value = dropEmptyElements(object[key]);
                if(value === undefined) {
                    delete object[key];
                }
                return value !== undefined;
            });
            return keys.length === 0 ? undefined : object;

        case"string":
            return object.length > 0 ? object : undefined;

        default:
            return object;
    }
}

This should do the trick for you ;)

Post a Comment for "Remove All Empty Strings, Empty Objects, And Empty Arrays From Js Object"