Merge JavaScript Objects in an Array with Different Defined Properties
Tags: javascript, typescript
Published: 2022-06-06 14:45:55

---
title: Merge JavaScript Objects in an Array with Different Defined Properties
published: true
date: 2022-06-06 00:00:00 UTC
tags: javascript,typescript
canonical_url: https://www.paigeniedringhaus.com/blog/merge-java-script-objects-in-an-array-with-different-defined-properties
---

[![Convergence of gold threads](https://www.paigeniedringhaus.com/static/de793cbaa3ebcfc1db0a7995b63fef02/15ec7/converge.jpg "Convergence of gold threads")](/static/de793cbaa3ebcfc1db0a7995b63fef02/7e809/converge.jpg)

## Introduction

At work recently, I encountered an interesting problem: I needed to merge multiple JavaScript objects together in an array based on their matching keys.

It sounds relatively straightforward at first, but there's more to the story that prevented me using the **[spread operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax#spread_in_object_literals)** or **[`Object.assign()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)** to combine these objects.

1. Each of these objects _might_ share a common key - it wasn't guaranteed, and
2. Although they all had the same properties in each object, not all of these properties were defined in every object - the object could have `property: value` or it could be `property: undefined`.

I needed to combine all the properties of objects that shared a common key along with their defined values into a new single object. And to make it just a smidge more difficult I needed it to work in TypeScript. 😅

**So today, I'll show you how to use JavaScript's `reduce()` function to merge two (or more) objects in an array with different defined properties.**

---

## Background: `motion.qo` and `air.qo` events

For those of you curious as to when this sort of thing comes up in real life, here's a little more context about my situation.

I work as a software engineer at an Internet of Things (IoT) startup named **[Blues Wireless](https://blues.io/?&utm_source=dev.to&utm_medium=web&utm_campaign=sparrow-accelerator&utm_content=merge-js-objects)**. Our main mission is to make IoT development simpler, regardless of if there's a reliable Internet connection or not. Blues does this via **[Notecards](https://blues.io/products/notecard/?&utm_source=dev.to&utm_medium=web&utm_campaign=sparrow-accelerator&utm_content=merge-js-objects)** - prepaid cellular devices that can be embedded into any IoT device "on the edge" to transmit that sensor data as JSON to a secure cloud: **[Notehub](https://blues.io/products/notehub/?&utm_source=dev.to&utm_medium=web&utm_campaign=sparrow-accelerator&utm_content=merge-js-objects)**.

In addition to producing this hardware and providing the secure cloud connection to send IoT data to, we also build software applications our users can connect their devices to and see the sensor data in charts and graphs.

[![Sample charts displaying sensor data](https://www.paigeniedringhaus.com/static/f2a8d7387296ed951131000ffdf43df2/1e043/charts.png "Sample charts displaying sensor data")](/static/f2a8d7387296ed951131000ffdf43df2/8bb81/charts.png)

_Here's an example of the charts displaying various sensor data._

One of the web apps we were building involved sensors that reported two types of data:

- **air quality** readings like temperature, humidity, sensor voltage, and pressure, and
- **motion** readings like current count of motions detected and total motions detected over time.

So a single sensor is generating two different JSON payloads that are sent from a Notecard to Notehub, then Notehub routes them to our web app to get rendered in the charts.

Here's a sample of some of the events delivered to the app.

**Example sensor events**

```javascript
const sensorEvents = [
  {
    sensorId: 'abc',
    id: '1',
    humidity: undefined,
    pressure: undefined,
    temperature: undefined,
    voltage: undefined,
    count: 3,
    total: 32,
    lastActivity: '2022-02-10T00:41:11Z'
  },
  {
    sensorId: 'abc',
    id: '2',
    humidity: 17.609375,
    pressure: 96.768734,
    temperature: 26.703125,
    voltage: 3.24,
    count: undefined,
    total: undefined,
    lastActivity: '2022-02-15T21:59:18Z'
  },
  {
    sensorId: 'def',
    id: '3',
    humidity: undefined,
    pressure: undefined,
    temperature: undefined,
    voltage: undefined,
    count: 866,
    total: 1776,
    lastActivity: '2022-02-15T22:00:03Z'
  }
]
```

As you can see, the first two objects in the array share the same `sensorId` of `abc`, but they have different defined properties.

The first object has `motion` properties (and undefined `air quality` properties), the second object has defined `air quality` properties (and undefined `motion` properties), and the third object has a different `sensorId` entirely; that is a `motion` reading from another sensor.

Which brought me to my problem: how to combine the objects with the same `sensorId` _and_ get all the defined properties from each object.

So, let's get to it.

### Merge like objects with a `mergeObject()` function and `reduce()`

With the amount of times I've said "merge" and "combine" in this post, you may already have figured out that the JavaScript array function **[`reduce()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce)** might come into play here, and it does.

At its most basic, `reduce()` goes through **each element** in an array and adds it to an **accumulator** of all the values preceding it, until just one value is returned at the end. The same thing can be done with an array of objects, with just a little more logic.

> If you'd like a more thorough explanation of `reduce()`, I encourage you to check out the [Mozilla documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce).
> 
> Their examples are great, and I still visit it regularly for all sorts of topics.

Below is the TypeScript-flavored solution I came up with to take an array of objects, find the ones that share a common key, and return a new array with those objects combined.

```typescript
interface HasSensorId {
  sensorId: string;
}

// merge objects with different defined properties into a single obj
const mergeObject = <V>(A: any, B: any): V => {
  let res: any = {};
  Object.keys({ ...A, ...B }).map((key) => {
    res[key] = B[key] || A[key];
  });
  return res as V;
};

// use Map functions `get`, `set`, and `values`
// take each event, and make it into a new Map() obj where sensorId is the key
// regardless of if there's already a value for that key, run the merge function
// set the key and the new merge var as the key, value for the map
const reducer = <V extends HasSensorId>(
  groups: Map<string, V>,
  event: V
) => {
  const key = event.sensorId;
  const previous = groups.get(key);
  const merged: V = mergeObject(previous || {}, event);
  groups.set(key, merged);
  return groups;
};

// run the sensor events through the reducer and then pull only their values into a new Map iterator obj
const reducedEventsIterator = sensorEvents
  .reduce(reducer, new Map())
  .values();

// transform the Map iterator obj back into an array
const reducedEvents = Array.from(reducedEventsIterator);
```

Ok, I realize there's quite a few functions above, so an explanation is in order.

### HasSensorId

The `HasSensorId` interface is pretty direct: it means that wherever this interface is referenced, the object's must have a string of `sensorId` among the properties.

Now let's walk through the rest of these functions one by one.

### mergeObject()

```typescript
const mergeObject = <V>(A: any, B: any): V => {
  let res: any = {};
  Object.keys({ ...A, ...B }).map((key) => {
    res[key] = B[key] || A[key];
  });
  return res as V;
};
```

This `mergeObject()` function is the one that actually does the combining of objects.

The `A` and `B` objects that this function takes in are the two objects with a common `sensorId` property, and the `res` variable will be the newly returned object of their properties (and values) combined.

The first thing that happens is the two objects are combined using the [spread operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax#spread_in_object_literals), and then just their keys are extracted into its own array using [`Object.keys()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign). If you passed the sample `sensorEvents` array into this function, the `Object.keys()` array would look like this:

```javascript
[
  'sensorId',
  'humidity',
  'pressure',
  'temperature',
  'voltage',
  'count',
  'total',
  'lastActivity'
]
```

After the keys are extracted, each key is added to the new `res` object as a key, and if the `B` object's value at that key exists (`B[key]`), it's added as the value for the `res` object, otherwise, the `A` object's value is added instead (even if it's `undefined`).

The final `res` object returned looks like this:

```javascript
{
  sensorId: 'abc',
  humidity: 17.609375,
  pressure: 96.768734,
  temperature: 26.703125,
  voltage: 3.24,
  count: 3,
  total: 32,
  lastActivity: '2022-02-15T21:59:18Z'
}
```

And finally the `V` and `any` references are part of the TypeScript implementation. From looking at the rest of the code, we can see `V` is an extension of the `HasSensorId` interface, which means any event object will have `sensorId` as a property. And `any` means the objects passed in to the function could have any properties (or not), so it's flexible.

Ok, on to the next function!

### reducer()

```typescript
const reducer = <V extends HasSensorId>(
  groups: Map<string, V>,
  event: V
) => {
  const key = event.sensorId;
  const previous = groups.get(key);
  const merged: V = mergeObject(previous || {}, event);
  groups.set(key, merged);
  return groups;
};
```

The `reducer()` function makes excellent use of true **[`Map` objects](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map)** in JavaScript. It uses the `HasSensorId` interface as well, ensuring that every object will have a `sensorId` property.

Each `event` object passed into the function, has its `sensorId` turned into a `key` variable. The `previous` variable relies on Map's [`get()` function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get) to fetch any other Map objects that have that same `sensorId` key, and if another object is found, the new `event` is combined with it when the `mergeObject()` function is called. If there's no matching `previous` object, an empty object is passed to `mergeObject()`, and a new `merged` variable is still returned.

Finally, the Map [`set()` function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/set) adds the `key` and `merged` variable to the `groups` Map object.

If our three `sensorEvents` are run through this function, the final `groups` object that emerges looks like this:

```javascript
Map(2) {
  'abc' => {
    sensorId: 'abc',
    humidity: 17.609375,
    pressure: 96.768734,
    temperature: 26.703125,
    voltage: 3.24,
    count: 3,
    total: 32,
    lastActivity: '2022-02-15T21:59:18Z'
  },
  'def' => {
    sensorId: 'def',
    humidity: undefined,
    pressure: undefined,
    temperature: undefined,
    voltage: undefined,
    count: 866,
    total: 1776,
    lastActivity: '2022-02-15T22:00:03Z'
  }
}
```

And so we move on to the penultimate part of this code example, `reducedEventsIterator`.

### reducedEventsIterator

```javascript
const reducedEventsIterator = sensorEvents
  .reduce(reducer, new Map())
  .values();
```

The `reducedEventsIterator` variable is where we call `reduce()` on the `sensorEvents` array. The `reducer()` function gets passed as the first argument each event in the array must run through, and a new `Map()` object is passed as the accumulator object where they will all be gathered together at the end.

Before the [`Object.values()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Object/values) method is chained on to the end, the array looks like this:

```javascript
[
  [
    'abc',
    {
      sensorId: 'abc',
      humidity: 17.609375,
      pressure: 96.768734,
      temperature: 26.703125,
      voltage: 3.24,
      count: 3,
      total: 32,
      lastActivity: '2022-02-15T21:59:18Z'
    }
  ],
  [
    'def',
    {
      sensorId: 'def',
      humidity: undefined,
      pressure: undefined,
      temperature: undefined,
      voltage: undefined,
      count: 866,
      total: 1776,
      lastActivity: '2022-02-15T22:00:03Z'
    }
  ]
]
```

After `.values()` is called, the final `reducedEventsIterator` looks more like this:

```javascript
[Map Iterator] {
  {
    sensorId: 'abc',
    humidity: 17.609375,
    pressure: 96.768734,
    temperature: 26.703125,
    voltage: 3.24,
    count: 3,
    total: 32,
    lastActivity: '2022-02-15T21:59:18Z'
  },
  {
    sensorId: 'def',
    humidity: undefined,
    pressure: undefined,
    temperature: undefined,
    voltage: undefined,
    count: 866,
    total: 1776,
    lastActivity: '2022-02-15T22:00:03Z'
  }
}
```

Which brings us to the final step: transforming this Map of objects back to a proper JavaScript array of objects (and all the benefits that entails - arrays are much easier to manipulate).

### The final merged array: reducedEvents

```javascript
const reducedEvents = Array.from(reducedEventsIterator);
```

Arguably the simplest part of this whole tutorial, the Map of objects is transformed back into an array simply by passing it into the [`Array.from()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from) method.

> **Beware `Array.from()` for copying deep objects**
> 
> If you have deeply nested objects in some sort of array-like structure that you need to copy, `Array.from()` probably won't work for you. It only makes shallow-copied instances of arrays.

So if we pass the example sensor events displayed above into our `reducedEventsIterator()` function here, this is what the final result looks like:

```javascript
[
  {
    sensorId: 'abc',
    humidity: 17.609375,
    pressure: 96.768734,
    temperature: 26.703125,
    voltage: 3.24,
    count: 3,
    total: 32,
    lastActivity: '2022-02-15T21:59:18Z'
  },
  {
    sensorId: 'def',
    humidity: undefined,
    pressure: undefined,
    temperature: undefined,
    voltage: undefined,
    count: 866,
    total: 1776,
    lastActivity: '2022-02-15T22:00:03Z'
  }
]
```

And there you have it: an array of freshly merged objects with all available properties present.

---

## Conclusion

One reason I enjoy being a software engineer is because of all the interesting problems I get to solve regularly. Lately, when I was building a web app to display data from IoT sensors at work, I ran into a more unique problem: taking an array of sensor events and merging the events together if they belonged to the same sensor. The catch was, all the events had the same properties, but depending on which event you were looking at, half the properties had values of `undefined`.

So instead of being able to use the spread operator or `Object.assign()` to get these events merged into one object, I had to get more creative with a few extra functions and the `reduce()` method. But it worked - even with TypeScript in the mix.

Check back in a few weeks — I’ll be writing more about JavaScript, React, IoT, or something else related to web development.

If you’d like to make sure you never miss an article I write, sign up for my newsletter here: https://paigeniedringhaus.substack.com

Thanks for reading. I hope this little bit of JavaScript helps you out in future - who'd have thought merging objects could be so interesting, right?

---

## References & Further Resources

- [Spread operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax#spread_in_object_literals) Mozilla docs
- [Object.assign()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) Mozilla docs
- [Map object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) Mozilla docs
- Blues Wireless [website](https://blues.io/?&utm_source=dev.to&utm_medium=web&utm_campaign=sparrow-accelerator&utm_content=merge-js-objects)
- JavaScript array method [reduce()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce)