Combining JavaScript objects within an array that share a common key

Can you provide an efficient way to combine array values from JavaScript objects that have a common key?

How would you rearrange the contents of the array into the format of output? In this scenario, all value keys (whether single value or array) should be merged within objects that share the same name key.

var array = [
    {
        name: "foo1",
        value: "val1"
    }, {
        name: "foo1",
        value: [
            "val2",
            "val3"
        ]
    }, {
        name: "foo2",
        value: "val4"
    }
];

var output = [
    {
        name: "foo1",
        value: [
            "val1",
            "val2",
            "val3"
        ]
    }, {
        name: "foo2",
        value: [
            "val4"
        ]
    }
];

Answer №1

Consider this alternative:-

let data = [{
  type: "car",
  model: "sedan"
}, {
  type: "car",
  model: ["SUV", "truck"]
}, {
  type: "bike",
  model: "sports"
}];

let finalResult = [];

data.forEach(function(item) {
  let present = finalResult.filter(function(v, i) {
    return v.type == item.type;
  });
  if (present.length) {
    let indexPresent = finalResult.indexOf(present[0]);
    finalResult[indexPresent].model = finalResult[indexPresent].model.concat(item.model);
  } else {
    if (typeof item.model == 'string')
      item.model = [item.model];
    finalResult.push(item);
  }
});

console.dir(finalResult);

Answer №2

If you're looking for an alternative approach to achieve the same objective, consider the following code snippet:

let data = [{
  name: "foo1",
  value: "val1"
}, {
  name: "foo1",
  value: [
    "val2",
    "val3"
  ]
}, {
  name: "foo2",
  value: "val4"
}];

let result = data.reduce(function(o, cur) {

  let index = o.reduce((acc, item, i) => {
    return (item.name === cur.name) ? i : acc;
  }, -1);

  if (index >= 0) {
    o[index].value = o[index].value.concat(cur.value);
  } else {
    let obj = {
      name: cur.name,
      value: [cur.value]
    };
    o = o.concat([obj]);
  }

  return o;
}, []);

console.log(result);

Answer №3

Updated for 2021

var arrays = [{ name: "foo1",value: "val1" }, {name: "foo1", value: ["val2", "val3"] }, {name: "foo2",value: "val4"}];

const result = arrays.reduce((acc, {name, value}) => {
  acc[name] ??= {name: name, value: []};
  if(Array.isArray(value)) // if it's array type then concat 
    acc[name].value = acc[name].value.concat(value);
  else
    acc[name].value.push(value);
  
  return acc;
}, {});

console.log(Object.values(result));

Answer №4

Utilizing the power of lodash library

let items = [{name:"item1",value:"val1"},{name:"item2",value:["val2","val3"]},{name:"item3",value:"val4"}];

function mergeItems (arr) {
    return _.chain(arr).groupBy('name').mapValues(function (v) {
        return _.chain(v).pluck('value').flattenDeep();
    }).value();
}

console.log(mergeItems(items));

Answer №5

Here is a code snippet that utilizes an ES6 Map:

const items = [{ title: "item1", description: "desc1" }, {title: "item2", description: ["desc2", "desc3"] }, {title: "item3",description: "desc4"}];

const map = new Map(items.map(({title, description}) => [title, { title, description: [] }])); 
for (let {title, description} of items) map.get(title).description.push(...[description].flat());
console.log([...map.values()]);

Answer №6

Utilizing the reduce method:

let mergedObject = array.reduce((accumulator, currentObject) => {
    if (accumulator[currentObject.name]) {
       accumulator[currentObject.name].value = accumulator[currentObject.name].value.isArray ? 
       accumulator[currentObject.name].value.concat(currentObject.value) : 
       [accumulator[currentObject.name].value].concat(currentObject.value);

    } else {
      accumulator[currentObject.name] = currentObject;
    }

    return accumulator;
}, {});

let finalOutput = [];
for (let property in mergedObject) {
  finalOutput.push(mergedObject[property]); 
}

Answer №7

Although it's been some time since this question was raised, I wanted to share my perspective on it as well. When dealing with functions like these that perform a basic operation repeatedly, I find it more efficient to avoid lengthy functions and loops whenever possible. Instead, I prefer crafting the function as a concise one-liner by utilizing Array.prototype methods like .map(), along with some modern ES6+ features such as Object.entries() and Object.fromEntries(). By combining these techniques, executing such a function becomes relatively straightforward.

Initially, I gather all the objects passed to the function as a rest parameter and prepend them with an empty object, which will serve as a repository for collecting keys and values.

[{}, ...objs]

Next, I use the .map() Array prototype method in conjunction with Object.entries() to iterate through each object's entries, including any sub-array elements within them. Subsequently, based on whether a key has already been declared or not in the empty object, I either set the value to that key if it hasn't been declared yet, or append new values to the existing ones.

[{},...objs].map((e,i,a) => i ? Object.entries(e).map(f => (a[0][f[0]] ? a[0][f[0]].push(...([f[1]].flat())) : (a[0][f[0]] = [f[1]].flat()))) : e)[0]

Finally, to eliminate single-element arrays and extract their contained values, I apply another .map() function to the resulting array, leveraging both Object.entries() and Object.fromEntries(), similar to the previous steps.

let getMergedObjs = (...objs) => Object.fromEntries(Object.entries([{},...objs].map((e,i,a) => i ? Object.entries(e).map(f => (a[0][f[0]] ? a[0][f[0]].push(...([f[1]].flat())) : (a[0][f[0]] = [f[1]].flat()))) : e)[0]).map(e => e.map((f,i) => i ? (f.length > 1 ? f : f[0]) : f)));

This approach will yield the final merged object exactly as specified.

let x = {
  a: [1,9],
  b: 1,
  c: 1
}
let y = {
  a: 2,
  b: 2
}
let z = {
  b: 3,
  c: 3,
  d: 5
}

let getMergedObjs = (...objs) => Object.fromEntries(Object.entries([{},...objs].map((e,i,a) => i ? Object.entries(e).map(f => (a[0][f[0]] ? a[0][f[0]].push(...([f[1]].flat())) : (a[0][f[0]] = [f[1]].flat()))) : e)[0]).map(e => e.map((f,i) => i ? (f.length > 1 ? f : f[0]) : f)));

getMergedObjs(x,y,z); // { a: [ 1, 9, 2 ], b: [ 1, 2, 3 ], c: [ 1, 3 ], d: 5 }

Answer №8

Implement lodash method "uniqWith" to combine values. See example below

const _ = require("lodash");

const array = [
  { name: "foo1", value: "1" },
  { name: "foo1", value: "2" },
  { name: "foo2", value: "3" },
  { name: "foo1", value: "4" }
];

const merged = _.uniqWith(array, (prev, current) => {
  if (prev.name === current.name) {
    current.value = `${current.value},${prev.value}`;
    return true;
  }
  return false;
});

console.log(merged);
// expected output: [{ name: "foo1", value: "1,2,4" }, { name: "foo2", value: "3" }];

Answer №9

Give this a shot:

let items = [{name:"item1",value:"val1"},{name:"item1",value:["val2","val3"]},{name:"item2",value:"val4"},{name:"item2",value:"val5"}];

for(let j=0;j<items.length;j++){
  let current = items[j];
  for(let i=j+1;i<items.length;i++){
    if(current.name === items[i].name){
      if(!Array.isArray(current.value))
        current.value = [current.value];
      if(Array.isArray(items[i].value))
         for(let v=0;v<items[i].value.length;v++)
           current.value.push(items[i].value[v]);
      else
        current.value.push(items[i].value);
      items.splice(i,1);
      i++;
    }
  }
}

function isArray(myArray) {
    return myArray.constructor.toString().indexOf("Array") > -1;
}

document.write(JSON.stringify(items));

Answer №10

This method works as well!

      var array = [
        {
          name: "foo1",
          value: "val1",
        },
        {
          name: "foo1",
          value: ["val2", "val3"],
        },
        {
          name: "foo2",
          value: "val4",
        },
      ];
      let arr2 = [];
      array.forEach((element) => { // check for duplicate names
        let match = arr2.find((r) => r.name == element.name);
        if (match) {
        } else {
          arr2.push({ name: element.name, value: [] });
        }
      });
      arr2.map((item) => {
        array.map((e) => {
          if (e.name == item.name) {
            if (typeof e.value == "object") { //map values if they are objects
              e.value.map((z) => {
                item.value.push(z);
              });
            } else {
              item.value.push(e.value);
            }
          }
        });
      });
      console.log(arr2);

Answer №11

const exampleObj = [{
  year: 2016,
  abd: 123
}, {
  year: 2016,
  abdc: 123
}, {
  year: 2017,
  abdcxc: 123
}, {
  year: 2017,
  abdcxcx: 123
}];
const listOfYears = [];
const finalObj = [];
exampleObj.map(sample => {    
  listOfYears.push(sample.year);
});
const uniqueList = [...new Set(listOfYears)];
uniqueList.map(list => {   
  finalObj.push({
    year: list
  });
});
exampleObj.map(sample => {    
  const sampleYear = sample.year;  
  finalObj.map((obj, index) => {     
    if (obj.year === sampleYear) {        
      finalObj[index] = Object.assign(sample, obj);       
    }  
  }); 
});

The resulting object will be [{"year":2016,"abdc":123,"abd":123},{"year":2017,"abdcxcx":123,"abdcxc":123}]

Answer №12

Give this code a try:

    var arr = [
      {
          title: "item1",
          val: "value1"
      }, {
          title: "item2",
          val: [
              "value2",
              "value3"
          ]
      }, {
          title: "item3",
          val: "value4"
      }
  ];
  
  var result = [
      {
          title: "item1",
          val: [
              "value1",
              "value2",
              "value3"
          ]
      }, {
          title: "item3",
          val: [
              "value4"
          ]
      }
  ];

  finalResult = Object.assign( {}, arr, result );

console.log(finalResult) ; 

Answer №13

let data = [{ title: "example1", content: "content1" }, {title: "example2", content: ["content2", "content3"]}, {title: "example3", content: "content4"}];
const initial = data.reduce((obj, {title}) => ({...obj, [title]: []}), {});
const finalResult = data.reduce((obj, {title, content}) => ({...obj, [title]: [obj[title], [content]].flat(2)}), initial);
const updatedOutput = Object.entries(finalResult).map(([title, content]) => ({title: title, content: content}));
console.log(updatedOutput);

Answer №14

Searching for a concise, almost "one-liner" response within this discussion, particularly as it pertains to a simple yet prevalent exercise.

I scoured through the replies but couldn't find one that suited my taste. While the other solutions are adequate, I prefer not to use boilerplate code excessively.

Therefore, allow me to contribute one here:

o = array.reduce((m,{name:n,value:v})=>({...m,[n]:[...m[n]||[],v].flat(1)}),{})
output = Object.entries(o).map(([n,v])=>({name:n,value:v}))

var array = [
  { name: "foo1", value: "val1"}, 
  { name: "foo1", value: ["val2","val3"] }, 
  { name: "foo2", value: "val4" }
]

o=array.reduce((m,{name:n,value:v})=>({...m,[n]:[...m[n]||[],v].flat(1)}),{})
output=Object.entries(o).map(([n,v])=>({name:n,value:v}))
    
console.log(output)

Answer №15

An easier method to achieve the desired result in 2022 is as follows:

let data = [
        {
            label: "item1",
            info: "info1"
        }, {
            label: "item1",
            info: [
                "detail1",
                "detail2"
            ]
        }, {
            label: "item2",
            info: "info2"
        }
    ];
    
    let mergedData = [
        {
            label: "item1",
            info: [
                "info1",
                "detail1",
                "detail2"
            ]
        },
        {
            label: "item2",
            info: [
                "info2"
            ]
        }
    ];
    
    function combineObjectsByKey(input){
      let combinedArray = Object.values(input.reduce((accumulator, current) => {
    
        accumulator[current.label] = accumulator[current.label] || {label: current.label, info: []}
        if(typeof(current['info']) == "string"){
          accumulator[current.label].info.push(current['info'])
        }
        else{
          accumulator[current.label].info = [...accumulator[current.label].info, ...current.info]
        }
    
        return accumulator
    
      }, {}))
      return combinedArray
    }
   let result = combineObjectsByKey(data)
   console.log(result)

Answer №16

If you find yourself in a situation similar to mine and are seeking a solution...

Although my circumstances were slightly different...

Here's how I resolved it using the _.mergeWith method:

// pre grouped array with _.groupBy
const source = {
    obj1: [{label: null, count: null }, {label: null, count: 10 }, {label: 'value1', count: null }],
    obj2: [{label: 'value2', count: 5 }, {label: null, count: null }],
    obj3: [{label: null, count: null }, {label: null, count: null }, {label: null, count: 15 }, {label: 'value3', count: null }],
    obj4: [{label: null, count: null }, {label: null, count: null }, {label: null, count: null }, {label: null, count: null }],
}

function combineObjects(key, source) {
    // spread the source array so not to mutate the original source with `.shift()` when getting the first object to merge with
    return _.mergeWith([...source[key]].shift(), ...source[key], (objValue, srcValue) => {
        if (srcValue !== null) return srcValue
        return objValue
    })
}

const combinedObjects = Object.keys(source).map(key => combineObjects(key, source))

// combinedObjects -> [{label: 'value1', count: 10 },{label: 'value2', count: 5 },{label: 'value3', count: 15 }, {label: null, count: null}]

Perhaps this approach could be beneficial to others facing a similar challenge.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

How to style date strings in JavaScript, jQuery, or CSS

Below are dates that need formatting: 7-sep, 14-sep, 21-sep, 28-sep, 5-oct,12-oct, 19-oct,26-oct, 2-nov, 9-nov, 16-nov, 23-nov,30-nov, 7-dec,14-dec, 21-dec,28-dec-2013. Is there a way to tidy them up using JavaScript, jQuery, or CSS? Ideally, I would lik ...

Navigate to the top of the Vue scrollable list using v-auto-complete

I need to implement a feature that automatically scrolls to the top of a scrollable list every time the search input is changed. Currently, I have a list where multiple items can be selected, and I want it to reset to the top whenever a new search query is ...

Cross domain widget ajax request

Our company offers a widget for clients to add to their websites. This widget requires a call to our website to validate user-entered data. However, due to domain restrictions, making ajax calls from the client's website to ours is not permitted. Is ...

Alter the value of a key in a multi-dimensional array using a different set of keys stored in

Consider I have two arrays: $arr = [ 'application' => [ 'foo' => [ 'bar' => 1 ] ] ]; $arr2 = [ 0 => 'application', 1 => 'foo', 2 => ' ...

Getting the value of a pin code in React Native using Haskkor

I am currently encountering an issue with my react native app. I have implemented a Pin code feature using @haskkor/react-native-pincode - npm. However, I am struggling to retrieve the value of the registered pin code from this npm package. My goal is to s ...

Displaying a Countdown in a Django Loop: A Step-by-Step

Hey there, I've got a Javascript countdown nested in a Django for loop that's causing some trouble by displaying multiple instances. Currently, only the first countdown works when I click on any of the start buttons. I'd like each button to ...

Trouble with Ruby on Rails jQuery interactions when using the tokenInput gem

Currently, I am developing a rails app and attempting to incorporate this gem for a text field https://github.com/exAspArk/rails-jquery-tokeninput Despite having the gem correctly installed and included in my _form.html.erb file, I keep encountering the ...

The hyperlinks in the navigation bar are leading me to incorrect destinations

I am currently developing an authentication app and facing an issue with the navbar links for register and login. They are not redirecting me to the correct destination. These links work perfectly on the landing index page, but not on any other page. site ...

Using an array of JSON objects to set up a Backbone.js bootstrap-initialized application

Trying to bootstrap a backbone collection by using an array of JSON objects has led to some unexpected errors. When attempting to call reset on the collection object, an error from Backbone is thrown - Uncaught TypeError: undefined is not a function. Inte ...

Firebase onSnapshot error when retrieving data from Snapchot

Having trouble with Firebase authentication. Whenever I try to authenticate with Firebase, I encounter this error message: App.js:27 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'onSnapshot') Here is the code sni ...

Customize your smartphone wallpaper with React Native

As a newcomer to React, I've successfully uploaded an image from my local phone gallery and stored its URI in a global variable. However, I'm struggling to figure out how to set this image as my phone's background image (not within React its ...

Once the email has been successfully sent and the modal has been called, the submit button should be deactivated permanently

Hello everyone, I need some help with a programming challenge I'm facing. I am trying to permanently disable the 'Approve' button, which sends an email using PHP and triggers a modal when clicked. I've attempted to use local storage to ...

What methods exist for determining the current location or city of a user without requesting permission?

Is there a way to automatically capture the user's current location on our website without explicit permission, possibly using IP address or MAC address? Has anyone successfully implemented this method in the past? ...

Error: Mapping values to cards resulted in a TypeError: Property 'map' cannot be read from undefined source

After following a tutorial from this website, I attempted to map my post details to cards. However, the error "TypeError: Cannot read property 'map' of undefined" popped up. Despite searching on various platforms like Stack Overflow for a soluti ...

Using NextJS getServerSideProps to Transfer Data to Page Component

Having trouble with my JavaScript/NextJS code, and not being an expert in these technologies. I'm using the method export const getServerSideProps: GetServerSideProps = async () => {} to fetch data from an integrated API within NextJS. The code mig ...

What is the method for displaying a canvas scene within a designated div element?

I need help displaying a scene inside an existing div on my webpage. Whenever I try to do this, the canvas is always added at the top of the page. I attempted to use getElementById but unfortunately, it didn't work as expected. What could I be overlo ...

How can I view call logs on an Android device using Cordova?

Looking to access recent call logs using Cordova? Unfortunately, there is no official plugin available for that. However, there is still hope with a custom plugin created by someone else. You can find the plugin here. The only issue is that the creator of ...

Ways to prevent the loading of images during webpage loading

I have encountered an issue with my eCommerce site developed using Laravel 7. Whenever I click on the category page, all product images are being loaded, causing high bandwidth usage. The category "Appereal" contains over 100 products, and although I imple ...

Establish a cookie using the PHP session's username

I have successfully implemented a general cookie for a one-time use scenario. However, I now need to create a cookie based on the username so that a message is displayed only once per user. My approach involves setting up a PHP session for the username ass ...

"Step-by-step guide on setting up Angularjs ng-model in a Rails environment

Question regarding the utilization of two-way binding in AngularJS and Rails ERB files. Let's say the input value in my .erb file has an initial value like this: example.erb <input type="text" value=" <%= @item.title %> " ng-model ="item.t ...