0

I have a complex react state that is structured like this:

{
   a: [
        {
           a: ['b','',]
         }
       ],
    b: {
         a: ['b',''],
         b: {c:'e',f:''}
       },
     c: 'd',
     e: '',
     f: ['g',''],
     date: new Date()
}

As you can see, could be in the object a lot of empty strings. Before I submit it to the backend, I would like to iterate over the state object and remove all empty values. So I first need to deep clone the state not to mess with my applications state which can be done by:

clone = JSON.parse(JSON.stringify(state))

but how do I iterate over the clone and remove all empty strings?

Meir
  • 516
  • 4
  • 19

2 Answers2

2

You can pass a replacer function into your JSON.stringify() method. Returning undefined from the replacement function will remove the key-value pair, so you can return undefined when you encounter an empty string. Likewise, when you encounter an array, you can filter it to only contain values which are non-empty strings.

See example below:

const state = {
  a: [{
    a: ['b', '', ]
  }],
  b: {
    a: ['b', ''],
    b: {
      c: 'e',
      f: ''
    }
  },
  c: 'd',
  e: '',
  f: ['g', ''],
  h: {},
  date: new Date()
};

const clone = JSON.parse(JSON.stringify(state, (key, value) => {
  if (typeof value === 'string' && value === "" || Object(value) === value && Object.keys(value).length === 0) {
    return undefined;
  } else if(Array.isArray(value)) {
    return value.filter(val => val !== "");
  }
  return value;
}));
console.log(clone);
Nick Parsons
  • 45,728
  • 6
  • 46
  • 64
  • I also want to remove array items that are empty objects, so I added this ` tempArr = tempArr.filter(val=>{ return !(val.constructor.name === "Object" && !Object.keys(val).length) })` but it does not work – Meir Sep 16 '20 at 15:39
  • @MeilechWieder I've updated my answer to handle the empty object case - First I check if the object is an object using `Object(value) === value`, and then check if it doesn't have any keys using `Object.keys(value) === 0`, if both values are met, you can return undefined. – Nick Parsons Sep 16 '20 at 15:47
1

You can also create a new object with a dedicated function that recursively removes the empty strings. Something like this:

const removeEmpties = (obj) => 
  Array .isArray (obj)
    ? obj .filter (x => x !== '') .map (removeEmpties)
  : Object (obj) === obj
    ? Object .fromEntries (Object .entries (obj) 
        .filter (([k, v]) => v !== '') 
        .map (([k, v]) => [k, removeEmpties (v)]
      ))
  : obj

const input = {a: [{a: ['b','',]}], b: {a: ['b',''], b: {c:'e',f:''}}, c: 'd', e: '', f: ['g',''], date: new Date()}

console .log (removeEmpties (input))
.as-console-wrapper {min-height: 100% !important; top: 0}

But that could be generalized to a more reusable functionlike this:

const deepFilter = (pred) =>  (obj) => 
  Array .isArray (obj)
    ? obj .filter (pred) .map (deepFilter (pred))
  : Object (obj) === obj
    ? Object .fromEntries (Object .entries (obj) 
        .filter (([k, v]) => pred(v)) 
        .map (([k, v]) => [k, deepFilter (pred) (v)]
      ))
  : obj

And then specialized for your case this way:

const removeEmpties = deepFilter (x => x !== '')
Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103
  • 1
    to make sure there are no empty arrays, objects or strings in nested object, this is the algorithm I ended up using. The base of the algorithm is yours. https://codesandbox.io/s/epic-faraday-qk8s9?file=/src/index.js – Meir Sep 17 '20 at 17:34
  • its not perfect, it might need some help – Meir Sep 17 '20 at 17:54
  • Feel free to post it here if you like. It's completely normal and acceptable to answer your own question. Note that some people behind corporate firewalls cannot see sites like codesandbox.io. – Scott Sauyet Sep 17 '20 at 18:18