Home > Software design >  reducing nested object key value inside an an array
reducing nested object key value inside an an array

Time:12-21

i have the following arrays

array1 = [
    {a:{key:1 , value: 10} , b:{key:1 , value:12} , c:{key:1 , value: 5} , d:{key:1 , value:2}},
    {a:{key:2 , value: 10} , b:{key:2 , value:12} , c:{key:2 , value: 5} , d:{key:2 , value:2}},
    {a:{key:3 , value: 10} , b:{key:3 , value:12} , c:{key:3 , value: 5} , d:{key:3 , value:2}},
]
array2 = [
    {a:{key:1 , value: 10} , b:{key:1 , value:12} , c:{key:1 , value: 5} , d:{key:1 , value:2}},
    {a:{key:2 , value: 10} , b:{key:2 , value:12} , c:{key:2 , value: 5} , d:{key:2 , value:2}},
    {a:{key:4 , value: 10} , b:{key:4 , value:12} , c:{key:4 , value: 5} , d:{key:4 , value:2}},
]

reduced array based on key should look like this:

combinedArray= [
    {a:{key:1 , value: 20} , b:{key:1 , value:24} , c:{key:1 , value: 10} , d:{key:1 , value:4}},
    {a:{key:2 , value: 20} , b:{key:2 , value:24} , c:{key:2 , value: 10} , d:{key:2 , value:4}},
    {a:{key:3 , value: 10} , b:{key:3 , value:12} , c:{key:3 , value: 5} , d:{key:3 , value:2}},
    {a:{key:4 , value: 10} , b:{key:4 , value:12} , c:{key:4 , value: 5} , d:{key:4 , value:2}},
]

first i tried to merge the two arrays using const mergedArray = [...array1, ...array2]

now i want to check for key duplicates. for example, if there is key1 in both array1 and array2, remove the duplicates then combine the values of that key.

this is what i have tried but it is only iterating through a.key only:

function kdeAdder(param) {
    const array = [param.a]
    let tempHistory = [];
    for(let x=0;x<array.length;x  ){
        array[x].forEach((item)=>{
            let noMatch = true; 
            if(tempHistory.length > 0) {
                tempHistory.forEach((tempItem, i)=>{
                    if(item.key === tempItem.key) {
                        tempHistory[i].value  = item.value;
                        noMatch = !noMatch; 
                    }
                });
            }
            return (noMatch) ? tempHistory.push(item) : null;
        });

    }
    return tempHistory;
}
kdeAdder(mergedArray);

CodePudding user response:

As you confirmed the key inner property is commonly shared by the four "a", "b", "c", "d" objects in an outer object, the a.key value can be used to identify which outer objects should merge.

You could group all objects (irrespective of whether they occur in array1 or array2) by that a.key, and then aggregate objects that occur in the same group. Both of these actions can be accomplished with a reduce call:

const aggregate = (objects) =>
    objects.reduce((x, y) => ({
        a: { key: x.a.key, value: x.a.value   y.a.value },
        b: { key: x.b.key, value: x.b.value   y.b.value },
        c: { key: x.c.key, value: x.c.value   y.c.value },
        d: { key: x.d.key, value: x.d.value   y.d.value },
    }));
                                
const merge = (array1, array2) =>
    Object.values(array1.concat(array2).reduce((acc, obj) => {
        (acc[obj.a.key] ??= []).push(obj);
        return acc;
    }, {})).map(aggregate);
    
const array1 = [
    {a:{key:1 , value: 10} , b:{key:1 , value:12} , c:{key:1 , value: 5} , d:{key:1 , value:2}},
    {a:{key:2 , value: 10} , b:{key:2 , value:12} , c:{key:2 , value: 5} , d:{key:2 , value:2}},
    {a:{key:3 , value: 10} , b:{key:3 , value:12} , c:{key:3 , value: 5} , d:{key:3 , value:2}},
];

const array2 = [
    {a:{key:1 , value: 10} , b:{key:1 , value:12} , c:{key:1 , value: 5} , d:{key:1 , value:2}},
    {a:{key:2 , value: 10} , b:{key:2 , value:12} , c:{key:2 , value: 5} , d:{key:2 , value:2}},
    {a:{key:4 , value: 10} , b:{key:4 , value:12} , c:{key:4 , value: 5} , d:{key:4 , value:2}},
]

console.log(merge(array1, array2));

CodePudding user response:

You could group with the key property and the outer proroperty.

const
    array1 = [{ a: { key: 1, value: 10 }, b: { key: 1, value: 12 }, c: { key: 1, value: 5 }, d: { key: 1, value: 2 } }, { a: { key: 2, value: 10 }, b: { key: 2, value: 12 }, c: { key: 2, value: 5 }, d: { key: 2, value: 2 } }, { a: { key: 3, value: 10 }, b: { key: 3, value: 12 }, c: { key: 3, value: 5 }, d: { key: 3, value: 2 } }],
    array2 = [{ a: { key: 1, value: 10 }, b: { key: 1, value: 12 }, c: { key: 1, value: 5 }, d: { key: 1, value: 2 } }, { a: { key: 2, value: 10 }, b: { key: 2, value: 12 }, c: { key: 2, value: 5 }, d: { key: 2, value: 2 } }, { a: { key: 4, value: 10 }, b: { key: 4, value: 12 }, c: { key: 4, value: 5 }, d: { key: 4, value: 2 } }],
    result = Object.values([...array1, ...array2].reduce((r, o) => {
        Object.entries(o).forEach(([k, { key, value }]) => {
            r[key] ??= {};
            r[key][k] ??= { key, value: 0 };
            r[key][k].value  = value;
        });
        return r;
    }, {}));

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

CodePudding user response:

You can first reduce the output to a single object since its a sort of accumulation of numbers, and then get the format you want as the second step.

const array1 = [ { a: { key: 1, value: 10 }, b: { key: 1, value: 12 }, c: { key: 1, value: 5 }, d: { key: 1, value: 2 }, }, { a: { key: 2, value: 10 }, b: { key: 2, value: 12 }, c: { key: 2, value: 5 }, d: { key: 2, value: 2 }, }, { a: { key: 3, value: 10 }, b: { key: 3, value: 12 }, c: { key: 3, value: 5 }, d: { key: 3, value: 2 }, }, ]; const array2 = [ { a: { key: 1, value: 10 }, b: { key: 1, value: 12 }, c: { key: 1, value: 5 }, d: { key: 1, value: 2 }, }, { a: { key: 2, value: 10 }, b: { key: 2, value: 12 }, c: { key: 2, value: 5 }, d: { key: 2, value: 2 }, }, { a: { key: 4, value: 10 }, b: { key: 4, value: 12 }, c: { key: 4, value: 5 }, d: { key: 4, value: 2 }, }, ];

const mergedArray = [...array1, ...array2];
const keys = []

const reducedOutput = mergedArray.reduce((prev, curr) => {
  Object.entries(curr).forEach(([mainKey, { key, value }]) => {
    // mainKey is a, b, c, d in your case
    if (!prev[mainKey]) {
      prev[mainKey] = {};
    }
    // key is 1, 2, 3, 4 in your case
    if (!keys.includes(key)) {
      keys.push(key)
    }
    prev[mainKey][key] = prev[mainKey][key]
      ? prev[mainKey][key]   value
      : value;
  });
  return prev;
}, {});

const output = keys.map(key => {
  const obj = {}
  Object.entries(reducedOutput).forEach(([k, v]) => {
    obj[k] = {key, value: v[key]}
  })
  return obj
})

console.log(output)

This will work with any other keys for a, b, c, d keys and 1, 2, 3, 4 keys you have used in two levels.

Using Object.entries(), Array.prototype.reduce(), Array.prototype.forEach(), and Array.prototype.map()

CodePudding user response:

You could loop through each object and then loop through each property in the object. Add each key to a group object and create an object as a value. If the key doesn't exist or the nested property doesn't exist, add them. Then increment the value

function merge(array) {
  const group = {};

  for (const item of array) {
    for (const k in item) {
      const { key, value } = item[k];
      group[key] ??= {}
      group[key][k] ??= { key, value: 0 }
      group[key][k].value  = value
    }
  }

  return Object.values(group)
}

const output = merge([...array1, ...array2])

Here's a snippet:

const array1 = [
    {a:{key:1 , value: 10} , b:{key:1 , value:12} , c:{key:1 , value: 5} , d:{key:1 , value:2}},
    {a:{key:2 , value: 10} , b:{key:2 , value:12} , c:{key:2 , value: 5} , d:{key:2 , value:2}},
    {a:{key:3 , value: 10} , b:{key:3 , value:12} , c:{key:3 , value: 5} , d:{key:3 , value:2}},
]
const array2 = [
    {a:{key:1 , value: 10} , b:{key:1 , value:12} , c:{key:1 , value: 5} , d:{key:1 , value:2}},
    {a:{key:2 , value: 10} , b:{key:2 , value:12} , c:{key:2 , value: 5} , d:{key:2 , value:2}},
    {a:{key:4 , value: 10} , b:{key:4 , value:12} , c:{key:4 , value: 5} , d:{key:4 , value:2}},
]

function merge(array) {
  const group = {};

  for (const item of array) {
    for (const k in item) {
      const { key, value } = item[k];
      group[key] ??= {}
      group[key][k] ??= { key, value: 0 }
      group[key][k].value  = value
    }
  }

  return Object.values(group)
}

console.log(
  merge([...array1, ...array2])
)

CodePudding user response:

The following provided code implements an approach which tries to find a balance in between 1st being agnostic to any array item's current and future structure except for both property names, key and value, of any array item's second level structure and 2nd how to handle the merger of other unknown second level data.

Therefore the general approach creates an object based lookup from the shorter sourceList where each item gets referred to via the value of its second level key property, whereas the longer targetList will be reduced in order to create the final result of merged items from both arrays.

Since approach and implementation are unaware of an items first level structure, one has to reduce again all of a currently processed item's entries. For each of a target item's unknown entry one can rely on such an entry's 2nd level properties, key and value. From all the available data, either known or unknown, one can aggregate the common merger of both the source- and the target-item; their values will be totaled and both of their unknown rest data will be merged by spread syntax, where the latter is the approach's trade off/compromise.

function aggregateFirstValueKeyBasedLookup(lookup, item) {
  lookup[Object.values(item)[0]?.key ?? ''] = item;
  return lookup;  
}
function createKeyBasedValueMergerFromSourceLookup(
  { lookup = {}, result = [] }, targetItem, idx, arr,
) {
  let currentLookupKey;

  result.push(Object
    .entries(targetItem)
    .reduce((merger, [
      targetEntryKey, {
        key, value: targetEntryValue = 0, ...targetEntryRest
      }
    ]) => {
      currentLookupKey = key;

      const sourceItem = lookup[key] ?? {};
      const {
        value: sourceEntryValue = 0, ...sourceEntryRest
      } = sourceItem[targetEntryKey] ?? {};

      return Object.assign(merger, {
        [ targetEntryKey ]: {
          key,
          value: (targetEntryValue   sourceEntryValue),
          ...targetEntryRest,
          ...sourceEntryRest,
        },            
      });

    }, {})
  );
  // delete already processed source-items from lookup.
  Reflect.deleteProperty(lookup, currentLookupKey);

  if (idx >= arr.length - 1) {
    // finalize the result by ...
    result.push(
      // ...pushing all of the lookup's
      //    unprocessed source-items.
      ...[...Object.values(lookup)]
    );
  }  
  return { lookup, result };
}


const array1 = [{
  a: { key: 1, value: 10 }, b: { key: 1, value: 12 }, c: { key: 1, value: 5 }, d: { key: 1, value: 2 }
}, {
  a: { key: 2, value: 10 }, b: { key: 2, value: 12 }, c: { key: 2, value: 5 }, d: { key: 2, value: 2 }
}, {
  a: { key: 3, value: 10 }, b: { key: 3, value: 12 }, c: { key: 3, value: 5 }, d: { key: 3, value: 2 }
}];
const array2 = [{
  a: { key: 1, value: 10 }, b: { key: 1, value: 12 }, c: { key: 1, value: 5 }, d: { key: 1, value: 2 }
}, {
  a: { key: 2, value: 10 }, b: { key: 2, value: 12 }, c: { key: 2, value: 5 }, d: { key: 2, value: 2 }
}, {
  a: { key: 4, value: 10 }, b: { key: 4, value: 12 }, c: { key: 4, value: 5 }, d: { key: 4, value: 2 }
}];

const [ targetList, sourceList ] =
  [array1, array2].sort((a, b) => b.length - a.length);

const sourceLookup = sourceList
  .reduce(aggregateFirstValueKeyBasedLookup, Object.create(null));

console.log({ sourceLookup });

const { result: mergedItemList } = targetList
  .reduce(createKeyBasedValueMergerFromSourceLookup, {
    lookup: sourceLookup, result: [],
  });

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

  • Related