Cloning JavaScript objects with Mangler.js

I was pretty busy lately watching all 5 seasons of Breaking Bad, but there were still quite a few improvements to Mangler.js since my last post. With many features taking final shape, it is time to write some posts focusing on when and how to use it, and why it might be useful.

In the introduction post I wrote a sneak-peak of how to extract parts of your data and how to create indices from object properties. I’ll get back to these topics in the future in more detail.

In this post I’ll use Mangler.js to create deep and shallow copies of objects.

Cloning (deep copy)

While many Mangler.js functions attempt to preserve references to the original data, sometimes you need an exact copy you can freely mess with without touching the original. The Mangler.clone() utility function creates and returns a deep copy of any object you pass to it:

var data = {
	id: 1,
	text: 'Hello world',
	fruit: ['apple', 'orange', 'banana'],
	now: new Date()
};

var dataClone = Mangler.clone(data);

The passed object will be processed recursively and the returned object will be an identical copy with no references pointing back to the original. Well, with one exception…

By default Mangler.js knows how to clone simple javascript objects and arrays, as well as Date objects and mangler objects. It is more than enough for processing simple JSON and literals, but if your data contains other object instances, they will not be cloned. If Mangler.js encounters an object it doesn’t recognise, it will pass its reference to the clone as is, pointing to the original object. Luckily, there is a way to teach Mangler.js how to clone other objects.

Cloning object instances

If you want to clone data that contains other native javascript types like typed arrays and Error objects, you can simply use the Mangler-natives module to add cloning support. Simply include mangler-natives.js after mangler.js to use. In case of your own types, you have to write the cloning function yourself and register it with Mangler.js:

// Define simple person object
function Person(firstname, surname) {
	this.firstname = firstname;
	this.surname = surname;
}

// Register cloning function
Mangler.registerType(Person, {
	clone: function(obj) {
		return new Person(obj.firstname, obj.surname);
	}
});

// Mangler.js can now clone person object instances
var person = new Person('John', 'Smith');
var personClone = Mangler.clone(person);

The above example creates a very simple Person object and specifies a cloning function to use. After calling .registerType(), Mangler.js will properly clone Person objects anywhere it finds it.

Standard cloning methods

In addition to a custom clone function, there are a couple of simple registration methods you can use if your object supports them.

The first option can be used if your object has a standard clone method that clones itself. In this case you can just set the clone parameter to true:

// Define simple person object
function Person(firstname, surname) {
	this.firstname = firstname;
	this.surname = surname;
}

// The person object has a standard clone method
Person.prototype.clone = function() {
	return new Person(this.firstname, this.surname);
};

// Register person object type
Mangler.registerType(Person, { clone: true });

The second supported standard can be used if your object has a copy constructor that clones the instance passed as a parameter:

// Define simple person object with copy constructor
function Person(person) {
	if(person) {
		this.firstname = person.firstname;
		this.surname = person.surname;
	}
}

// Method to set the name
Person.prototype.setName = function(firstname, surname) {
	this.firstname = firstname;
	this.surname = surname;
};

// Register person object type
Mangler.registerType(Person, { clone: 'constructor' });

Merging (shallow copy)

The Mangler.merge() function gets two object parameters: a destination and a source. It copies all properties from source to destination and returns the destination object. In case of matching property names, destination properties will be overwritten by source:

var part1 = { firstname: 'John' };
var part2 = { surname: 'Smith' };

var person = Mangler.merge(part1, part2);

// person points to part1 which now has both properties

By passing an empty object literal as destination, it can be effectively used to create a shallow copy of a javascript object:

var person = { firstname: 'John', surname: 'Smith' };
var personCopy = Mangler.merge({}, person);

You can freely modify the resulting object and add/remove properties as you like without affecting the original, but as it is only a shallow copy, modifying any object that’s stored in its properties will affect the original data:

var person = {
	firstname: 'John',
	surname: 'Smith',
	contact: {
		email: 'john.smith@example.com',
	}
};
var personCopy = Mangler.merge({}, person);

// firstname changes in copy only
personCopy.firstname = 'Fred';

// email changes in both
personCopy.contact.email = 'fred.smith@example.com';

Merging arrays of objects

Either parameter of the merge function can be an array of objects which modifies its behaviour.

If both parameters are arrays, it will merge each object in the destination array with its corresponding object in the source array:

var dst = [{ number: 1 }, { number: 2 }, { number: 3 }];
var src = [{ roman: 'I' }, { roman: 'II' }, { roman: 'III' }];

Mangler.merge(dst, src);

// dst objects now have both representations of the numbers:
// { number: 1, roman: 'I' }
// { number: 2, roman: 'II' }
// { number: 3, roman: 'III' }

If only destination is an array, it will merge the single source object into all objects in the destination array:

var dst = [{ number: 1 }, { number: 2 }, { number: 3 }];
var src = { digits: 1 };

Mangler.merge(dst, src);

// dst:
// { number: 1, digits: 1 }
// { number: 2, digits: 1 }
// { number: 3, digits: 1 }

Finally, if only the source parameter is an array, it will merge each source object into the single destination object in order:

var dst = { one: 1 };
var src = [{ two: 2 }, { three: 'oops' }, { three: 3 }];

Mangler.merge(dst, src);

// dst: { one: 1, two: 2, three: 3 }
Share Button
Tagged with: ,

Leave a Reply