Learn To Clone Like A Sith Lord
Tags: react, javascript, recursion, tutorial
Published: 2020-04-17 20:54:55

[**NOTE:** The cloning utilities discussed in this article are now in their own NPM package.  You can find them here: https://www.npmjs.com/package/@toolz/clone]

I'm going to highlight the strengths-and-weaknesses of "native" methods for cloning objects/arrays.  Then I'm going to show how to create a custom, recursive approach that will faithfully clone **_ALL THE THINGS_**.

In most programming languages, objects (and their nephews, arrays) are passed by reference.  This is an incredibly useful (and powerful) concept that can be leveraged to do all sorts of impressive things.  But one instance where it can feel like a hindrance is when we need to get a full, fresh, clean, standalone _copy_ of an object/array.  In other words, there are times when you want a full-fledged _clone_ of an object/array.  But this process is not exactly "straight forward".

## Tricky References

The simplest version of an object might look something like this:

```javascript
const phantomMenace = { master: 'palpatine', apprentice: 'maul' };
```

One of the first _gotchas_ that new devs run into is when they try to "copy" the object, like this:

```javascript
const phantomMenace = { master: 'palpatine', apprentice: 'maul' };
const attackOfTheClones = phantomMenace;
attackOfTheClones.apprentice = 'dooku';
console.log(phantomMenace.apprentice);  // dooku(!)
```

Code like this is a common source of confusion.  Just by giving it a quick read-through, it's easy to come to the (mistaken) conclusion that `phantomMenace` and `attackOfTheClones` are each independent entities.  Continuing with this (flawed) logic, it's tempting to think that `console.log(phantomMenace.apprentice);` will output `'maul'`, because the value was set to 'maul' in the `phantomMenace` object, and it was only set to `'dooku'` _on the `attackOfTheClones` object_, and not _on the `phantomMenace` object_.

Of course, the reality's quite different.  `attackOfTheClones` is not a standalone entity.  Instead, it's nothing but a pointer referring back _to the original `phantomMenace` object_.  So when we update the contents of `attackOfTheClones`, the change is also reflected in `phantomMenace`.

For this reason, it can sometimes be desirable to have a true, clean, standalone _copy_ of an object/array.  An entity that has all the same information as its source - but will act independently after we've copied it.  In other words, sometimes we need a full _clone_ of an object/array.

## Spread Operators

One very fast, very easy way to clone objects is with the new(ish) spread operator.  That would look like this:

```javascript
const phantomMenace = { master: 'palpatine', apprentice: 'maul' };
const attackOfTheClones = {...phantomMenace};
attackOfTheClones.apprentice= 'dooku';
console.log(phantomMenace.apprentice);  // maul
```

This is so simple that it's tempting to throw out all of your "old" object-cloning tools in favor of spread operators.  Unfortunately, this is only "simple" when the object you're cloning is simple.  Consider this slightly-more-complex example:

```javascript
const phantomMenace = { 
  master: 'palpatine', 
  apprentice: 'maul',
  henchmen: {
    one: 'nute gunray',
    two: 'rune haako',
  },
};
const attackOfTheClones = {...phantomMenace};
attackOfTheClones.henchmen.one = 'jar jar binks';
console.log(phantomMenace.henchmen.one);  // jar jar binks(!)
```

We're back to the original problem.  We "cloned" `phantomMenace`.  Then we made a change to `attackOfTheClones`.  And then the change was reflected _in the original `phantomMenace` object_.  Why did this happen?  

The problem occurs because _all_ objects are passed by reference, not just _the parent object_.  In the example above, there are _two_ objects - one nested inside the other.  

Using the spread operator, a brand new object was created as `attackOfTheClones`.  However, when the spread operator was doing its magic, it encountered _another_ object when it reached the `henchmen` key.  So it copied that object over _by reference_.  This brings us right back to square one.

Theoretically, you can address this problem by doing this:

```javascript
const phantomMenace = { 
  master: 'palpatine', 
  apprentice: 'maul',
  henchmen: {
    one: 'nute gunray',
    two: 'rune haako',
  },
};
const attackOfTheClones = {
  ...phantomMenace,
  henchmen: {...phantomMenace.henchmen},
};
attackOfTheClones.henchmen.one = 'jar jar binks';
console.log(phantomMenace.henchmen.one);  // nute gunray
```

But this solution is far-from-scaleable.  We can't use `attackOfTheClones = {...phantomMenace}` with universal confidence that it will "just work".  We have to manually reconfigure our use of the spread operator every time we're dealing with a multilevel object.  _Yech..._  And if our object has many nested layers, we need to recreate all those layer with many nested spread operators.  _Many nested Yechs..._

## JSON.parse(JSON.stringify())

This is the solution that I've used for all of my "lightweight" object/array cloning.  It uses JSON serialization/de-serialization to break the "connection" between a copied object and its source object.  `JSON.stringify()` converts it into a plain-ol' string - with no knowledge of the originating object.  (Because strings are passed _by value_, not _by reference_.)  `JSON.parse()` converts it _back_ into a full-fledged JavaScript object, that still bears no connection to the originating object. 

This approach looks like this:

```javascript
const phantomMenace = { 
  master: 'palpatine', 
  apprentice: 'maul',
  henchmen: {
    one: 'nute gunray',
    two: 'rune haako',
  },
};
const attackOfTheClones = JSON.parse(JSON.stringify(phantomMenace));
attackOfTheClones.henchmen.one= 'jar jar binks';
console.log(phantomMenace.henchmen.one);  // nute gunray
``` 

It has some strong features in its favor:

* It maintains scalar data types.  So if a value was a Boolean, or a number, or `NULL` before it was copied, the cloned version will have those same data types.

* It's perfectly fine if the source object contains _other_ objects (or arrays).

* It's inherently recursive.  So if your source object has 100 nested layers of objects, those will be fully represented in the cloned object.

So is this the ultimate answer??  Umm... not really.  I leverage this technique on a fairly regular basis, but it fails entirely when you have more "complex" items in your object.  

Consider this example:

```javascript
const phantomMenace = { 
  master: 'palpatine', 
  apprentice: 'maul',
  henchmen: {
    one: 'nute gunray',
    two: 'rune haako',
    fearLeadsTo: () => console.log('the dark side'),
  },
};
const attackOfTheClones = JSON.parse(JSON.stringify(phantomMenace));
console.log(attackOfTheClones.henchmen.fearLeadsTo()); 
``` 
Oops.

The console tells us `Uncaught TypeError: attackOfTheClones.henchmen.fearLeadsTo is not a function`.  This happens because functions don't survive the serialization process.  This is a pretty big _gotcha_ because most modern JavaScript frameworks - like React - are heavily based upon the idea that our objects can contain functions.  

There's another nasty problem with this approach that presents itself in React.  It comes up when you try to do this:

```javascript
export default function StarWars() {
  const phantomMenace = { key: <Prequel1/>};
  const attackOfTheClones = JSON.parse(JSON.stringify(phantomMenace));
  return <div>A long time ago, in a galaxy far far away...</div>;
}
```

This example won't even _compile_.  It throws an error that reads `TypeError: Converting circular structure to JSON`.  Explaining exactly _why_ that happens would require an entirely new post.  Just suffice it to say that you can't serialize React components.  And in a large enough app, it's not uncommon to find that you occasionally have objects that contain React components.

## Third-Party Cloning Tools

Obviously, I'm not the first person to ponder these challenges.  And there are a number of NPM utilities that will allow you to get a deep clone of an object or an array.  I don't have any "problem" with such utilities.  I'm not going to review them all here.  You can have fun googling all those solutions on your own.  Some of them are quite good.  

But one of my pet peeves is when we import all sorts of outside packages/libraries to do something in JavaScript that we could easily do on our own with plain ol' programming.  The reason why most people don't code this up on their own is because, to do it properly, you need to use recursion.  And recursion feels to many devs like... _the dark side_.

## Cloning the Sith Way

If we want to "clone like a Sith lord", there's no way that I know to accomplish it without going to the dark side.  In other words, we must utilize recursion.  Since every object/array can contain a theoretically-endless number of nested objects/arrays, we can't get by with a simple for/while loop.  We need something that has the ability to call _itself_.  This isn't "hard".  But it steps outside of some devs' comfort zones.

First, let's create a decent test object that will ensure our cloning utilities will truly rise to the task.  I'll be using this:

```javascript
const original = {
  one: '1',
  two: '2',
  nest1: {
    four: '4',
    five: '5',
    header: <SiteHeader/>,
    nest2: {
      seven: '7',
      eight: '8',
      function1: () => console.log('the function'),
    },
    nest3: [
      {
        john: 'doe',
        mary: 'mack',
      },
      {
        butcher: 'brown',
        karen: 'conroy',
      },
      <AnotherComponent/>,
    ],
  },
};
```
This is a fairly robust object.  We have objects inside objects.  We have an array inside a (nested) object.  We have a function inside one of the nested objects.  We have a React component inside one of the nested objects.  We have another React component inside the nested array.  

First, I want a convenient way to test whether something is an object or an array.  To do that, I'm going to use my `is()` utility.  I wrote about that here: 
https://dev.to/bytebodger/javascript-type-checking-without-typescript-21aa

Second, the logic for recursively cloning an object is slightly different than the logic for recursively cloning an array.  So I'm going to create two separate, but interdependent, functions.  

The code looks like this:

```javascript
const cloneArray = (originalArray = []) => {
  const suppressError = true;
  if (!is.anArray(originalArray))
    return;
  return originalArray.map(element => {
    if (React.isValidElement(element))
      return element; // valid React elements are pushed to the new array as-is
    if (is.anObject(element, suppressError))
      return cloneObject(element); // push the CLONED object to the new array
    if (is.anArray(element, suppressError))
      return cloneArray(element);  // push the CLONED array to the new array
    return element;  // if it's neither an array nor an object, just push it to the new array
  });
};

const cloneObject = (originalObject = {}) => {
  const suppressError = true;
  if (!is.anObject(originalObject))
    return;
  let clonedObject = {};
  Object.keys(originalObject).forEach(key => {
    const currentValue = originalObject[key];
    if (React.isValidElement(currentValue))
      clonedObject[key] = currentValue; // valid React elements are added to the new object as-is
    else if (is.anObject(currentValue, suppressError))
      clonedObject[key] = cloneObject(currentValue);  // set this key to the CLONED object
    else if (is.anArray(currentValue, suppressError))
      clonedObject[key] = cloneArray(currentValue);  // set this key to the CLONED array
    else
      clonedObject[key] = currentValue;  // if it's neither an object nor an array, just set this key to the value
  });
  return clonedObject;
};
```

Notice that when we're drilling through an object/array, and we find _another_ object/array, we need to (again) call `cloneObect()` or `cloneArray()`.  This ensures that we keep calling `cloneObject()` or `cloneArray()` until we finally reach an object/array that has no child objects/arrays.  In other words, we have to do this _recursively_.

So let's put this to the test:

```javascript
const original = {
  one: '1',
  two: '2',
  nest1: {
    four: '4',
    five: '5',
    header: <SiteHeader/>,
    nest2: {
      seven: '7',
      eight: '8',
      function1: () => console.log('the function'),
    },
    nest3: [
      {
        john: 'doe',
        mary: 'mack',
      },
      {
        butcher: 'brown',
        karen: 'conroy',
      },
      <AnotherComponent/>,
    ],
  },
};
const clone = cloneObject(original);
original.nest1.nest2.eight = 'foo';
console.log(clone);
clone.nest1.nest2.function1();
```

This passes the test.  Merely by calling `cloneObject()`, we created a true, deeply-nested clone of the original object.  

The cloning process throws no errors.  The function sitting at `clone.nest1.nest2.function` has survived the cloning process and can be called directly as part of `clone`.  The React components that were in `original` are now transferred over to `clone` and can be used in any standard way you would expect to use a React component.  Even though we made a subsequent change to `original.nest1.nest2.eight`, that change _is not reflected in `clone`_.  

In other words:  `clone` is a true, deep clone of `original`, reflecting the exact state of `original` at the time we created the clone (but not reflecting any _future_ changes that were made to `original`).

Also, by leveraging two inter-dependent functions, there's no need to start the cloning process with an object.  If you need to clone an array, you can call `cloneArray()`, and that should work the same way, even if the array has many, complex, nested layers - and even if some of those layers consist of objects.