0
\$\begingroup\$

I'm trying to implement a function (a template-helper function) which provides a simple merge-like functionality for both arrays and objects. The two functions are basically looks identical:

const mergeArrays = (first, ...rest) =>
  rest.reduce((acc, curr) => ([ ...acc, ...curr]), first);

const mergeObjects = (first, ...rest) =>
  rest.reduce((acc, curr) => ({ ...acc, ...curr }), first);

One could do a type check and a local variable to write a single function like this:

const mergeArraysOrObjects1 = (first, ...rest) => {
  let reducer = null;
  if ('object' === typeof first && null !== first) {
    reducer = (acc, curr) => ({ ...acc, ...curr });
  }
  if (Array.isArray(first)) {
    reducer = (acc, curr) => [...acc, ...curr];
  }

  if (!reducer) {
    throw new Error('The "merge" filter only works with arrays and hashes.');
  }

  return rest.reduce(reducer, first);
}

... but it seems so ugly to me. Another possibility is a type check and inline conditional:

const mergeArraysOrObjects2 = (first, ...rest) => {
  if ('object' !== typeof first || null === first) {
    throw new Error('The "merge" filter only works with arrays and hashes.');
  }

  return rest.reduce(
    (acc, curr) => Array.isArray(acc) ? [...acc, ...curr] : {...acc, ...curr},
    first
  );
}

I'm still learning JavaScript so I'm guessing if there is a more elegant and concise way to treat the return value the same way.

\$\endgroup\$
1
\$\begingroup\$

Object.assign && Array.flat

By the first example which does not have any type checking makes the throwing of errors in the second two examples look superfluous, a little over kill.

As the function Array.isArray is the more concise statement over the always ugly object test typeof obj === "Object && obj !== null you can use a simple ternary to call the correct function

You can use Object.assign rather than the long and slow object by object reduce method to merge objects.

And for the array Array.push, then Array.flat(1) depth of 1 for the arrays. To ensure you do not effect the input array first you would also need to copy it to a new array

Examples.

const merge = (first, ...rest) => {
    const arrays = (arr) => (arr.push(...rest), arr.flat(1));
    const objects = () => Object.assign({}, first,  ...rest);        
    return Array.isArray(first) ? arrays([[...first]]) : objects();
}

Or a shortened form

const merge = (first, ...rest) => {
    return Array.isArray(first) ? 
         (first = [[...first]], first.push(...rest), first.flat(1)) : 
         Object.assign({}, first,  ...rest);
}

Update

Fixed bug as pointed out in comments.

From the same comment by ScottyJamison in which is presented an even better solution, using the rest operator on all the arguments resulting in...

const merge = (...items) => Array.isArray(items[0]) ? 
    items.flat(1) : 
    Object.assign({}, ...items);

or

const merge = (...items) => Array.isArray(items[0]) ? items.flat(1) : Object.assign({}, ...items);
\$\endgroup\$
2
  • \$\begingroup\$ Your example is broken - if the first array has subarrays, they would get flattened, which is different from the O.P.'s implementation. Why not just do to this? const merge = (...items) => Array.isArray(items[0]) ? items.flat(1) : Object.assign({}, ...items); \$\endgroup\$ Dec 31 '20 at 18:23
  • \$\begingroup\$ @ScottyJamison Yes thank you very much for pointing that out. Will fix it. :) \$\endgroup\$
    – Blindman67
    Dec 31 '20 at 18:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.