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;
};