Home > Enterprise >  Iterate object tree with arrays and objects to clean out null/undefined or empty string using node.j
Iterate object tree with arrays and objects to clean out null/undefined or empty string using node.j

Time:06-10

So, I've found a ton of good suggestions on how to iterate over an object "tree" and filter out null, undefined, etc. which all works fine for objects and attributes.

I am trying to clean a object tree that has a ton of arrays:

"cac:Delivery": [
  {
    "cac:DeliveryLocation": [
      {
        "cbc:Name": [
          {
            "_": "Company Name"
          }
        ],
        "cac:Address": [
          {
            "cbc:StreetName": [
              {
                "_": "The Street 123"
              }
            ],
            "cbc:AdditionalStreetName": [
              {
                "_": null
              }
            ],
            "cbc:CityName": [
              {
                "_": "The City"
              }
            ],
            "cbc:PostalZone": [
              {
                "_": ""
              }
            ],
            "cac:Country": [
              {
                "cbc:IdentificationCode": [
                  {
                    "_": ""
                  }
                ]
              }
            ]
          }
        ]
      }
    ]
  }
]

The above sample is a snippet of the full message and why it looks like this is because the original is a UBL XML message that we run xml2js on to transform it from XML to JSON.

We then need to run some other mappings and extraction of values on the tree. The next step of the flow won't accept any null, undefined or empty string as values.

The problem I have a that I can't figure out any nifty way of traversing the tree with the arrays and clean out the tree (=remove the "empty" attributes).

In the sample, cleaning out cbc:IdentificationCode will then of course make cac:Country to be empty in turn...

There are hundreds of "groups" that I need to "clean" so I need to come up with something dynamic and it is also important that the order of attributes are kept...

The above should result in:

"cac:Delivery": [
  {
    "cac:DeliveryLocation": [
      {
        "cbc:Name": [
          {
            "_": "Company Name"
          }
        ],
        "cac:Address": [
          {
            "cbc:StreetName": [
              {
                "_": "The Street 123"
              }
            ],
            "cbc:CityName": [
              {
                "_": "The City"
              }
            ]
          }
        ]
      }
    ]
  }
]

EDIT: The data is based upon UBL JSON representation: https://docs.oasis-open.org/ubl/UBL-2.1-JSON/v2.0/cnd01/UBL-2.1-JSON-v2.0-cnd01.html

Various samples can be found here: https://docs.oasis-open.org/ubl/UBL-2.1-JSON/v2.0/cnd01/json/ (I am currently working on the "Invoice" message)

EDIT2: Figured to share to "iterator" I came up with that is traversing the entire tree and handles each Object/Array/Attribute:

function iterate(obj) {
  Object.entries(obj).forEach(([key, value]) => {
    console.log('KEY:', key);
    if (Array.isArray(obj[key])) {
      console.log('ARRAY');
      obj[key].forEach((k) => iterate(k));
    } else if (typeof obj[key] === 'object') {
      console.log('OBJECT');
      if (obj[key]) {
        iterate(obj[key]);
      }
    } else {
      console.log('Key / Value:', key, value);
      if (!value) {
        console.log('THIS IS AN EMPTY VALUE!');
      }
    }
  });
}

CodePudding user response:

ElasticObject is a wrapper for objects that adds array methods to objects. I would use that and than its filter method.

CodePudding user response:

Well, maybe not the most elegant of solutions but after a few hours of pondering this I solved it using lodash _.set() which can create a object tree from paths... I called my function deepClean() and this now returns an object without any nullish values (but I do want to keep 0's):

function iterate(obj, arrObj, path = '') {
  Object.entries(obj).forEach(([key, value]) => {
    // Construct the path to the object/array that we are currently looking at
    const newPath = `${path}${path ? '.' : ''}['${key}']`;
    if (Array.isArray(obj[key])) {
      // If it is an array we have to add the [index] to the path as well, then iterate over the array
      obj[key].forEach((k, index) => iterate(k, arrObj, `${newPath}[${index}]`));
    } else if (typeof obj[key] === 'object') {
      // If it's an object, and the object has any child/value iterate again
      if (obj[key]) {
        iterate(obj[key], arrObj, newPath);
      }
    } else {
      // If this is a "value" push it to the Array
      arrObj.push([`${path}.${key}`, value]);
    }
  });
}

const deepClean = (obj) => {
  // We need to clean the whole object tree and remove any array/object/key that are "nullish"
  // however, we must keep values as zero (number) as e.g. VAT can be zero
  const arrObj = [];
  const newObj = {};
  // Call itarate() to loop through the entire tree recursivly and build an array as [path-to-object, value]
  iterate(obj, arrObj);
  arrObj.forEach((o) => {
    // Start creating the new cleaned object from the values/paths using lodash _.set()
    if (typeof o[1] === 'number' || o[1]) {
      _.set(newObj, o[0], o[1]);
    }
  });
  return newObj;
};

  • Related