2

To give some background: By using Postman (the REST api tool) we are comparing XMLs to a template by converting the XMLs to JSON and compare those as Javascript objects. The comparison can handle wildcards in the values and will return a new JS object (or JSON) with only the differences. When there are no differences, I receive an empty object which is the correct state. In some cases empty values or objects are returned and we remove them from the object with a clean step.

This is how the clean function looks like:

Utils = {
  clean: function(object) {
    Object
        .entries(object)
        .forEach(([k, v]) => {
            if (v && typeof v === 'object') 
                Utils.clean(v);
            if (v && typeof v === 'object' && !Object.keys(v).length || v === null || v === undefined) 
                Array.isArray(object) ? object.splice(k, 1) :  delete object[k]; 
        });
    return object;
  }
}

This works fine for most cases except when we have an array with multiple the same empty object because of the object.splice in combination with the foreach as pointed out here.

Normally, I would use a filter function, use _.pickBy from lodash or iterate backwards through the array, but because of the layout of the clean function, I can not figure out how to do that.

Can you help me to point out what I need to do to remove multiple empty items and objects from an array correctly.

Real life testcase:

var x = {"Document":{"CstmrDrctDbtInitn":{"GrpHdr":{},"PmtInf":{"DrctDbtTxInf":[{"PmtId":{}},{"PmtId":{}},{"PmtId":{}},{"PmtId":{}},{"PmtId":{}}]}}}};
console.log(JSON.stringify(Utils.clean(x)));
// returns {"Document":{"CstmrDrctDbtInitn":{"PmtInf":{"DrctDbtTxInf":[{},{}]}}}}
// desired result: {}

Other testcases:

console.log(JSON.stringify(Utils.clean({"a": [null,null,"b","c",{},{},{},{}]})));
// returns {"a":[null,"c",{},{},{}]} 
// desired: {"a":["b", "c"]}

console.log(JSON.stringify(Utils.clean({"a": [null,null,"b","c",{"d": {}},{}]})));
// returns {"a":[null,"c",{},{}]} 
// desired: {"a":["b", "c"]}

console.log(JSON.stringify(Utils.clean({ "a" : [null,null,{"d": {}, "e": [null, {}]},{}]})));
// returns {"a":[null,{}]} 
// desired: {}
AutomatedChaos
  • 7,267
  • 2
  • 27
  • 47

1 Answers1

1

Give this a shot, and here's a working example: https://jsfiddle.net/3rno4L7d/

Utils Object (with extra helpers)

const Utils = {
  doDelete: function(val) {
    return !Boolean(val) ||
      Utils.isEmptyObj(val) ||
      Utils.isEmptyArray(val);
  },
  isEmptyArray: function(val) {
    return Array.isArray(val) && val.length === 0;
  },
  isEmptyObj: function(obj) {
    return Object.keys(obj).length === 0 &&
      obj.constructor === Object;
  },
  hasKeys: function(obj) {
    return Object.keys(obj).length > 0;
  },
  clean: function(object) {
    Object
      .keys(object)
      .forEach(key => {
        const val = object[key];

        // If dealing with an object, clean it.
        if (val && typeof val === 'object') {
            Utils.clean(val);
        }

        // If deleteable, delete and return
        if (Utils.doDelete(val)) {
          delete object[key];
          return object;
        }

        // If array, loop over entries
        if (Array.isArray(val)) {
          let i = val.length;

          // While lets us delete from the array without affecting the loop.
          while (i--) {
            let entry = val[i];
            // If deleteable, delete from the array
            if (Utils.doDelete(entry)) {
              val.splice(i, 1)
            } else if (Utils.hasKeys(entry)) {
              // If an object, clean it
              entry = Utils.clean(entry);
              // Check to see if cleaned object is deleteable
              if (Utils.doDelete(entry)) {
                val.splice(i, 1)
              }
            }
          }
          // Once done with the array, check if deleteable
          if (Utils.doDelete(val)) {
            delete object[key];
          }
        }
      });
    return object;
  }
}

Output

console.log(JSON.stringify(Utils.clean({"a": [null,null,"b","c",{},{},{},{}]})));
// Returns {"a":["b","c"]}

console.log(JSON.stringify(Utils.clean({"a": [null,null,"b","c",{"d": {}},{}]})));
// Returns {"a":["b","c"]}

console.log(JSON.stringify(Utils.clean({ "a" : [null,null,{"d": {}, "e": [null, {}]},{}]})));
// Returns {}
lux
  • 8,315
  • 7
  • 36
  • 49
  • Thanks @lux, also for the extra effort in creating the extra helpers. As JS is not my primary language, I struggled a bit with it with all the curious undefined, empty, etc. The "real life case" didn't fully work recursively with your code, but I improved it a bit, you can find it [here](https://jsfiddle.net/oLm5328u/). Maybe you want to add the extra line (basically just this: `if (val && typeof val === 'object') { Utils.clean(val);}` ), so the next person that wants to use it also does have that in. – AutomatedChaos Feb 12 '19 at 19:06
  • @AutomatedChaos Cool, I updated the answer with the extra bit. Could even check for deletion after the very first clean, similar to the `else if` in the `while` loop, but if all is working out for you, no worries. – lux Feb 12 '19 at 19:33