4

I have n objects(with some nested structure). In the end, I'm expecting to get one merged object with all fields.

const result = {
  firstParam: {
    x: {
      id1: ["somedata1", "somedata2"],
      id3: ["somedata1", "somedata2"],
    },
    y: {
      id1: ["somedata1", "somedata2"],
      id3: ["somedata1", "somedata2"],
    },
    z: {
      id1: ["somedata1", "somedata2"],
      id3: ["somedata1", "somedata2"],
    }
  },
  secondParam: {
    x: {
      id1: ["somedata1", "somedata2"],
      id3: ["somedata1", "somedata2"],
    },
    y: {
      id1: ["somedata1", "somedata2"],
      id3: ["somedata1", "somedata2"],
    },
    z: {
      id1: ["somedata1", "somedata2"],
      id3: ["somedata1", "somedata2"],
    }
  },
  thirdParam: {
    x: {
      id1: ["somedata1", "somedata2"],
      id3: ["somedata1", "somedata2"],
    },
    y: {
      uuid1: ["somedata1", "somedata2"],
      id3: ["somedata1", "somedata2"],
    },
    z: {
      id1: ["somedata1", "somedata2"],
      id3: ["somedata1", "somedata2"],
    }
  }
}

const newObj1 = {
  firstParam: {
    x: {
      id2: ["somedata1", "somedata2"]
    },
    y: {
      id2: ["somedata1", "somedata2"]
    },
    z: {
      id2: ["somedata1", "somedata2"]
    }
  },
  secondParam: {
    x: {
      id2: ["somedata1", "somedata2"]
    },
    y: {
      id2: ["somedata1", "somedata2"]
    },
    z: {
      id2: ["somedata1", "somedata2"]
    }
  },
  thirdParam: {
    x: {
      id2: ["somedata1", "somedata2"]
    },
    y: {
      id2: ["somedata1", "somedata2"]
    },
    z: {
      id2: ["somedata1", "somedata2"]
    }
  }
}


const merge = (result, newObj) => {
  console.time("test merge")
  Object.keys(result).forEach(key => {
    result[key] = {
      x: {
        ...result[key].x,
        ...newObj[key].x
      },
      y: {
        ...result[key].y,
        ...newObj[key].y
      },
      z: {
        ...result[key].z,
        ...newObj[key].z
      }
    }
  })
  console.timeEnd("test merge")

  return result;
}

merge(result, newObj1);

This solution gives me an expected result(I'm getting an obj2, obj3 ... objn and every iteration I'm merging it to result), but when I'm getting more than 500 or 1000 newObj it works pretty slow. Is there any way to optimize it?

Lajos Arpad
  • 64,414
  • 37
  • 100
  • 175
ItsMyLife
  • 458
  • 7
  • 21
  • I tried not creating a new object every time, and using `in` instead of `Object.keys`, but I didn't see any improvement that would be statistically significant, at least in V8. I don't think there are any *strong* improvements to be made – CertainPerformance Feb 20 '20 at 09:54
  • Maybe try Object.assign?: newObject = Object.assign({}, firstObject, secondObject) – Andris Feb 20 '20 at 10:02
  • This is discussed in https://stackoverflow.com/questions/27936772/how-to-deep-merge-instead-of-shallow-merge – The Coprolal Feb 20 '20 at 14:44

3 Answers3

1

The problem is that you are always doing rest operations with ... here

const merge = (result, newObj) => {
  console.time("test merge")
  Object.keys(result).forEach(key => {
    result[key] = {
      x: {
        ...result[key].x,
        ...newObj[key].x
      },
      y: {
        ...result[key].y,
        ...newObj[key].y
      },
      z: {
        ...result[key].z,
        ...newObj[key].z
      }
    }
  });

Imagine an object of 10 000 elements destructured into elements and then another array created in its place. The larger the array, the more memory write will happen and the slower the code will get. Instead, you should create an array only once and then add elements into it, like:

const merge = (result, newObj) => {
  //we assume that result already of the format of:
  //{x: {/*...*/}, y: {/*...*/}, z: {/*...*/}}
  console.time("test merge");
  for (let outerKey in newObj) { //firstParam, secondParam, thirdParam
    for (let innerKey in newObj[outerKey]) { //x, y, z
      for (let valueKey in newObj[outerKey][innerKey]) {
        result[outerKey][innerKey][valueKey] = newObj[outerKey][innerKey][valueKey];
      }
    }
  }
};
Lajos Arpad
  • 64,414
  • 37
  • 100
  • 175
1

I assume you don't know the key naming of id1, id2, id3 ...

The performance issue is the spread operator, using a loop to get the key name and then executing only the needed substitution will make your code much faster.

import { Suite } from 'benchmark';

let suite = new Suite();


const result = {
    firstParam: {
        x: {
            id1: ["somedata1", "somedata2"],
            id3: ["somedata1", "somedata2"],
        },
        y: {
            id1: ["somedata1", "somedata2"],
            id3: ["somedata1", "somedata2"],
        },
        z: {
            id1: ["somedata1", "somedata2"],
            id3: ["somedata1", "somedata2"],
        }
    },
    secondParam: {
        x: {
            id1: ["somedata1", "somedata2"],
            id3: ["somedata1", "somedata2"],
        },
        y: {
            id1: ["somedata1", "somedata2"],
            id3: ["somedata1", "somedata2"],
        },
        z: {
            id1: ["somedata1", "somedata2"],
            id3: ["somedata1", "somedata2"],
        }
    },
    thirdParam: {
        x: {
            id1: ["somedata1", "somedata2"],
            id3: ["somedata1", "somedata2"],
        },
        y: {
            uuid1: ["somedata1", "somedata2"],
            id3: ["somedata1", "somedata2"],
        },
        z: {
            id1: ["somedata1", "somedata2"],
            id3: ["somedata1", "somedata2"],
        }
    }
}

const newObj1 = {
    firstParam: {
        x: {
            id2: ["somedata1", "somedata2"]
        },
        y: {
            id2: ["somedata1", "somedata2"]
        },
        z: {
            id2: ["somedata1", "somedata2"]
        }
    },
    secondParam: {
        x: {
            id2: ["somedata1", "somedata2"]
        },
        y: {
            id2: ["somedata1", "somedata2"]
        },
        z: {
            id2: ["somedata1", "somedata2"]
        }
    },
    thirdParam: {
        x: {
            id2: ["somedata1", "somedata2"]
        },
        y: {
            id2: ["somedata1", "somedata2"]
        },
        z: {
            id2: ["somedata1", "somedata2"]
        }
    }
}


const merge = (result: any, newObj: any) => {
    Object.keys(result).forEach(key => {
        result[key] = {
            x: {
                ...result[key].x,
                ...newObj[key].x
            },
            y: {
                ...result[key].y,
                ...newObj[key].y
            },
            z: {
                ...result[key].z,
                ...newObj[key].z
            }
        }
    })
    return result;
}

const mergeNoSpread = (result: any, newObj: any) => {
    Object.keys(result).forEach(key => {
        for (const key2 in newObj[key].x) {
            result[key].x[key2] = newObj[key].x[key2]
        }
        for (const key2 in newObj[key].y) {
            result[key].y[key2] = newObj[key].y[key2]
        }
        for (const key2 in newObj[key].z) {
            result[key].z[key2] = newObj[key].z[key2]
        }
    })

    return result;
}

suite
    .add('Spread usage', () => { merge(result, newObj1); })
    .add('No spread usage', () => { mergeNoSpread(result, newObj1); })
    .on('cycle', function (event: any) {
        console.log(String(event.target));
    })
    .on('complete', function () {
        console.log('Fastest is: ' + this.filter('fastest').map('name'));
    }).run({ 'async': true });


The performance results:

Spread usage x 794,766 ops/sec ±1.43% (87 runs sampled)
No spread usage x 2,176,879 ops/sec ±0.78% (88 runs sampled)
Fastest is: No spread usage

One iteration using console.time:

test merge: 0.252ms
test merge: 0.088ms
costadvl
  • 128
  • 2
  • 12
  • Here you can see a performance comparison using a direct assignation [jsPerf](https://jsperf.com/the-cost-of-spreading/1) – costadvl Feb 20 '20 at 11:15
0

I afford make it run in the half time by replacing object keys iteration with for in:

Old:

results[key] = {
    x: {
        ...results[key].x,
        ...newObject[key].x
    },
    y: {
        ...results[key].y,
        ...newObj1[key].y
        },
    z: {
           ...results[key].z,
           ...newObj1[key].z
        }
    }
})  

New:

for (let key in results) {
    results[key] = {
        x: {
            ...results[key].x,
            ...newObj[key].x
        },
        y: {
            ...results[key].y,
            ...newObj[key].y
        },
        z: {
          ...results[key].z,
          ...newObj[key].z
        }
    }   
}
StPaulis
  • 2,844
  • 1
  • 14
  • 24