Copying properties from one object to another (including Getters and Setters)
Tags: javascript
Published: 2020-08-13 04:39:53

---
title: Copying properties from one object to another (including Getters and Setters)
description: What it takes to copy properties from one object to another. Also, how to copy accessors from one object to another.
canonical_url: https://zellwk.com/blog/copy-properties-of-one-object-to-another-object
tags: javascript
newsletter: jsSnippets
cover_image: https://zellwk.com/images/2020/copy-accessors/accessors-not-copied.png
published: true
---

`Object.assign` is the standard way to copy properties from one object to another. It is often used for copying properties that are one-layer deep. (One-layer deep means there are no nested objects).

It can be used to extend settings from a default object. Here's an example:

```js
const one = { one: 'one' }
const two = { two: 'two' }
const merged = Object.assign({}, one, two)

console.log(merged) // { one: 'one', two: 'two' }
```

Unfortunately, `Object.assign` doesn't copy accessors. (Accessor is a term for Getter and Setter functions). `Object.assign` reads the value of a Getter function and copies that value instead.

```js
let count = 0
const one = {}
const two = {
  get count () { return count },
  set count (value) { count = value }
}
const three = Object.assign({}, one, two)

console.log('two:', two)
console.log('three:', three)
```

Try logging `two` and `three` in a Node environment. Accessors will be logged clearly. You'll immediately see that `three.count` is NOT an accessor.

<figure role="figure">
  <img src="https://zellwk.com/images/2020/copy-accessors/accessors-not-copied.png" alt="Accessors are not copied into three.">
</figure>


## Copying Accessors

[MDN's article][1] about `Object.assign` states this. If you want to copy accessors, you need to:

1. Get the property's descriptor with `Object.getOwnPropertyDescriptor`
2. Create a property with `Object.defineProperty`

### Object.getOwnPropertyDescriptor

`Object.getOwnPropertyDescriptor` tells you more information about a property. This information includes:

1. `value`: Value of the property (if any)
2. `get`: Getter function (if any)
3. `set`: Setter function (if any)
4. `writable`: Whether the property can be edited
5. `configurable`: Whether the property can be edited and deleted
6. `enumerable`: Whether the property can be enumerated

We don't need to use advanced features like `writable`, `configurable`, and `enumerable` normally. So there's no need to use `getPropertyDescriptor` much in practice.

Syntax:

```js
const descriptor = Object.getOwnPropertyDescriptor(object, 'property')
```

If you grab a normal property, you'll see a `value` key.

```js
const object = {
  normalProperty: 'hello world',
}

const descriptor = Object.getOwnPropertyDescriptor(object, 'normalProperty')
console.log(descriptor)
```

```js
// Output
// {
//   value: 'hello world',
//   writable: true,
//   enumerable: true,
//   configurable: true
// }
```

<figure role="figure">
  <img src="https://zellwk.com/images/2020/normal-prop.png" alt="Descriptor of a normal property.">
</figure>

If you log the descriptor of an accessor, you'll see `get` and `set` keys.

```js
let count = 0
const two = {
  get count () { return count }
  set count (value) { count = value }
}

const descriptor = Object.getOwnPropertyDescriptor(two, 'count')
console.log(descriptor)
```

<figure role="figure">
  <img src="https://zellwk.com/images/2020/accessor.png" alt="Descriptor of an accessor.">
</figure>

### Object.getDefineProperty

`Object.defineProperty` lets you create a property. It lets you configure the same 6 values you find in `Object.getOwnPropertyDescriptor`.

1. `value`: Value of the property (if any)
2. `get`: Getter function (if any)
3. `set`: Setter function (if any)
4. `writable`: Whether the property can be edited
5. `configurable`: Whether the property can be edited and deleted
6. `enumerable`: Whether the property can be enumerated

`Object.defineProperty` can only be used after the object is created.

Syntax:

```js
Object.defineProperty(object, property, desciptor)
```

Example:

```js
const object = {}
Object.defineProperty(object, 'normalProperty', { value: 'Hello world'})

console.log(object) // { normalProperty: 'Hello world' }
```

There's no need to use `Object.defineProperty` for normal properties, unless you want to change the `writable`, `configurable`, or `enumerable` settings.

If you simply need to create a property with a value, you can use notation we're used to:

```js
// Same result as above
const object = {}
object.normalProperty = 'Hello world'
```

`Object.defineProperty` is useful when you need to create accessors AFTER an object is created. This is because accessor shorthands can only be used when you create the object. They cannot be used afterwards.

```js
// Creating a `count` getter function with Accessor shorthands
const object = {
  get count () {}
}
```

If you want to add an accessor to a defined object , you need `Object.defineProperty`

```js
// Same result as above
const object = {}
Object.defineProperty(object, 'count', {
  get function () {
    return count
  }
}
```

### Copying accessors

If we want to copy an accessor from one object to another, we can:

1. Get the descriptor with `Object.getOwnPropertyDescriptor`
2. Create the property with `Object.defineProperty`

Here's an example:

```js
let count
const original = {
  get count () { return count },
  set count (value) { count = value }
}
const copy = {}

const descriptor = Object.getOwnPropertyDescriptor(original, 'count')
Object.defineProperty(copy, 'count', descriptor)

console.log('copy:', copy)
```

<figure role="figure">
  <img src="https://zellwk.com/images/2020/copy-accessors/copied-accessor.png" alt="Copied the `count` accessor.">
</figure>

### Copying all properties of an object

It's easy to copy all properties of an object once you know how to copy one. You can loop through all enumerable properties and run the same two lines of code.

```js
const original = {
  normalProperty: 'hello world',
  get count () { return count },
  set count (value) { count = value }
}
const copy = {}

// Copies all properties from original to copy
const props = Object.keys(original)
for (const prop of props) {
  const descriptor = Object.getOwnPropertyDescriptor(original, prop)
  Object.defineProperty(copy, prop, descriptor)
}

console.log('copy:', copy)
```

<figure role="figure">
  <img src="https://zellwk.com/images/2020/copy-accessors/copied-many-include-accessors.png" alt="Copied all properties, including accessors.">
</figure>

### Merging different object sources

If we want to copy properties from multiple sources, we need to create a function that takes in all possible sources. Let's call this function `mix`.

```js
function mix (...sources) {
  // ...
}
```

We will then loop through each source and copy properties into a new object.

```js
function mix (...sources) {
  const result = {}
  for (const source of sources) {
    const props = Object.keys(source)
    for (const prop of props) {
      const descriptor = Object.getOwnPropertyDescriptor(source, prop)
      Object.defineProperty(result, prop, descriptor)
    }
  }
  return result
}
```

`mix` can be used like `Object.assign` now.

```js
let count = 0
const one = { one: 'one' }
const two = { two: 'two' }
const three = {
  get count () { return count },
  set count (value) { count = value }
}
const mixed = mix({}, one, two, three)

console.log('mixed:', mixed)
```

<figure role="figure">
  <img src="https://zellwk.com/images/2020/copy-accessors/mix-simple.png" alt="Combined properties and accessors into a new object with mix">
</figure>

The great part is `mix` doesn't mutate objects. You don't have o pass in an empty object.

```js
// Produces the same result as above
const mixed = mix(one, two, three)
```

## Shallow Merge vs Deep Merge

`Object.assign` doesn't work well with nested objects. If you copy a nested object, that nested object can still be mutated.

```js
const one = {}
const two = { nested: { value: 'two' } }
const three = Object.assign({}, one, two)

// Nested values are mutated when changed
three.nested.value = 'three'
console.log(two.nested.value) // 'three'
```

Our `mix` function works the same way as `Object.assign`. That's not ideal.

```js
// Same result as above
const one = {}
const two = { nested: { value: 'two' } }
const three = mix(one, two)

// Nested values are mutated when changed
three.nested.value = 'three'
console.log(two.nested.value) // 'three'
```

Both `Object.assign` and `mix` perform what we call a **shallow merge**. A shallow merge is when you copy and paste first-layer properties completely into a new object. Properties belonging to a nested object still get pointed to the same reference.

Note: if you're confused "references", read [this analogy about Identity cards][2]. It'll clear things up.

We don't want nested objects to point to the same references because it can mutate without us knowing.  [This kind of mutation is a source of hard-to-find bugs][3]. We want to perform a deep merge instead (where we create new versions of nested objects in the new object).

### Ways to Deep Merge

Many people have created ways to perform deep merging already. Examples include:

1. [Assignment][4] by [Nicolás Bevacqua][5]
2. [Merge-options][6] by [Michael Mayer][7]
3. [Deepmerge][8] by [Josh Duff][9]

These libraries work like `Object.assign`.

1. You pass in a comma-separated list of objects to merge.
2. The library will merge the object and it will return a new object.

There are slight differences though.

`assignment` works exactly like `Object.assign`. The first object you passed in will get mutated. So you need to pass in an empty object.

```js
const one = {}
const two = { nested: { value: 'two' } }
const three = assignment({}, one, two)
```

`merge-options` and `deepmerge` creates an empty object for you automatically. So you don't have to pass in an empty object as the first argument.

```js
const mergeOoptions = require('merge-options')

const one = {}
const two = { nested: { value: 'two' } }
const three = mergeOptions(one, two)
```

While testing this, I discovered a bug with `deepmerge`. If you pass an empty object as the first argument, `deepmerge` will return an empty object. Not sure why.

```js
const deepmerge = require('deep-merge')

const one = {}
const two = { nested: { value: 'two' } }
const three = deepmerge({}, one, two)

console.log(three) // {} ....... 🤷‍♂️
```

**Unfortunately, none of these methods support the copying of accessors.**

```js
const mergeOoptions = require('merge-options')

let count = 0
const one = {}
const two = {
  get count () { return count } ,
  set count (value) { count = value }
}
const three = mergeOptions(one, two)

console.log('two:' two)
console.log('three:', three)
```

<figure role="figure">
  <img src="https://zellwk.com/images/2020/copy-accessors/accessors-not-copied.png" alt="Merge options doesn't copy accesors into the new object.">
</figure>

### Deep Merging that includes Accessors

I couldn't find a library that lets you perform a deep merge while copying accessors. I don't know why people haven't created it yet 😢.

So I went ahead and created one. It's called `mix`. [Here's the code for mix](https://github.com/zellwk/javascript/tree/master/mix). (I'll explain how I created `mix` in the next article, which should be fun!).

Let me tell you what `mix` is capable of.

### Two features of `mix`

First, `mix` copies accessors.

```js
let count = 0
const one = {}
const two = {
  get count () { return count },
  set count (value) { count = value }
}
const three = mix(one, two)

console.log('two:', two)
console.log('three:', three)
```

<figure role="figure">
  <img src="https://zellwk.com/images/2020/copy-accessors/yes-copy-accessors.png" alt="Mix copies accessors.">
</figure>

Second, `mix` copies nested objects and arrays so you don't have to worry about mutation.

```js
const one = {}
const two = { nested: { value: 'two' } }
const three = mix(one, two)

// Nested values do not get mutated
three.nested.value = 'three'
console.log(two.nested.value) // 'two'
```

That's it!

I'd appreciate it if you take `mix` out for a spin and let me know if you have any feedback!

[1]:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
[2]:https://zellwk.com/blog/js-if-else/#comparing-objects-and-arrays
[3]:https://alistapart.com/article/why-mutation-can-be-scary/
[4]:https://github.com/bevacqua/assignment/blob/master/assignment.js
[5]:https://twitter.com/nzgb
[6]:https://twitter.com/nzgb
[7]:https://github.com/schnittstabil
[8]:https://www.npmjs.com/package/deepmerge
[9]:https://github.com/TehShrike

<hr>

Thanks for reading. This article was originally posted on [my blog](https://zellwk.com/blog/copy-properties-of-one-object-to-another-object/).  Sign up for [my newsletter](https://zellwk.com/) if you want more articles to help you become a better frontend developer.