#2691 - Immutability Helper
Problem Description
Creating clones of immutable objects with minor alterations can be a tedious process.
Write a class ImmutableHelper that serves as a tool to help with this requirement.
The constructor accepts an immutable object obj which will be a JSON object or array.
The class has a single method produce which accepts a function mutator. The function returns a new object which is similar to the original except it has those mutations applied.
mutator accepts a proxied version of obj. A user of this function can (appear to) mutate this object, but the original object obj should not actually be effected.
For example, a user could write code like this: const originalObj = {"x": 5}; const helper = new ImmutableHelper(originalObj); const newObj = helper.produce((proxy) => { proxy.x = proxy.x + 1; }); console.log(originalObj); // {"x": 5} console.log(newObj); // {"x": 6} Properties of the mutator function:
- It will always return undefined.
- It will never access keys that don't exist.
- It will never delete keys (delete obj.key)
- It will never call methods on a proxied object (push, shift, etc).
- It will never set keys to objects (proxy.x = {})
Note on how the solution will be tested: the solution validator will only analyze differences between what was returned and the original obj. Doing a full comparison would be too computationally expensive. Also, any mutations to the original object will result in a wrong answer.
Solution
var ImmutableHelper = function(obj) {
this.original = obj;
};
/**
* @param {Function} mutator
* @return {JSON} clone of obj
*/
ImmutableHelper.prototype.produce = function(mutator) {
const mutated = { _: this.original };
mutator(proxify(mutated, { _: this.original }, (field, value) => {
mutated[field] = value;
return mutated;
})._);
return mutated._;
function proxify(mutableObj, originalObj, setter) {
return new Proxy(mutableObj, {
set(_, property, value) {
mutableObj = setter(property, value);
},
get(_, property) {
let value = mutableObj[property];
if (!value || typeof value !== 'object') {
return value;
} else {
return proxify(value, originalObj[property], (field, newValue) => {
if (value === originalObj[property]) {
mutableObj = setter(
property,
value = Array.isArray(value) ? [...value] : { ...value }
);
}
value[field] = newValue;
return value;
});
}
}
});
}
};