
Problem Statement
The task is to create a function that receives an object obj
, which is a valid JSON object or an array, and returns a new immutable version of this object. Once an object is defined as immutable, any attempt to modify its properties (for objects) or elements (for arrays), as well as calls to methods that typically mutate arrays, should result in specific error messages. The error messages should clearly indicate the type of modification attempt:
- Modifying an object property will yield
Error Modifying: ${key}
. - Modifying an array element will yield
Error Modifying Index: ${index}
. - Using mutating array methods (like
push
,pop
, etc.) will yieldError Calling Method: ${methodName}
.
This immutable object ensures that the original structure and values remain unchanged after its creation, throwing errors when any change attempts are detected.
Examples
Example 1
Input:
obj = { "x": 5 } fn = (obj) => { obj.x = 5; return obj.x; }
Output:
{ "value": null, "error": "Error Modifying: x" }
Explanation:
Attempting to modify a key on an object results in an error, even if setting the same value.
Example 2
Input:
obj = [1, 2, 3] fn = (arr) => { arr[1] = {}; return arr[2]; }
Output:
{ "value": null, "error": "Error Modifying Index: 1" }
Explanation:
Attempting to modify an array element throws an error.
Example 3
Input:
obj = { "arr": [1, 2, 3] } fn = (obj) => { obj.arr.push(4); return 42; }
Output:
{ "value": null, "error": "Error Calling Method: push" }
Explanation:
Calling a mutating array method throws an error.
Example 4
Input:
obj = { "x": 2, "y": 2 } fn = (obj) => { return Object.keys(obj); }
Output:
{ "value": ["x", "y"], "error": null }
Explanation:
No mutation occurs, so the function returns normally.
Constraints
obj
is a valid JSON object or array2 <= JSON.stringify(obj).length <= 10^5
Approach and Intuition
To solve this problem:
Clone the Original Object: Use
JSON.parse(JSON.stringify(obj))
to create a deep copy of the input. This avoids mutation of the original object.Wrap with a Proxy: Use
Proxy
to trap and intercept operations like property setting, deletion, and method calls.Intercept Property Modification:
- For object properties, throw
Error Modifying: ${key}
if a set operation is attempted. - For array elements, throw
Error Modifying Index: ${index}
.
- For object properties, throw
Override Mutating Array Methods: Block methods such as
push
,pop
,shift
,unshift
,splice
,sort
, andreverse
. Each should throwError Calling Method: ${methodName}
when invoked.Apply Recursively: Ensure immutability is enforced deeply by applying the proxy recursively to all nested objects and arrays.
Result Handling: Execute the provided function
fn
with the proxied immutable object and capture its return value or intercept thrown errors to format the output accordingly.
This structured proxy-based approach ensures strict immutability and clear error messaging as per the problem requirements.
Solutions
- JavaScript
var createImmutableObject = function(inputObject) {
const mutatingMethods = new Set(['pop', 'push', 'shift', 'unshift', 'splice', 'sort', 'reverse']);
const proxyHandler = {
set: function(tgt, key) {
throw Array.isArray(tgt) ? `Error Modifying Index: `{key}` : `Error Modifying: `{key}`;
},
get: function(tgt, key) {
const value = tgt[key];
const isAllowed = key === 'prototype' || value === null || (
typeof value !== 'object' && typeof value !== 'function'
);
return isAllowed ? value : new Proxy(value, this);
},
apply: function(target, context, args) {
if (mutatingMethods.has(target.name)) {
throw `Error Calling Method: ${target.name}`;
}
return Reflect.apply(target, context, args);
}
}
return new Proxy(inputObject, proxyHandler);
};
Create an immutable object in JavaScript by defining a function named createImmutableObject
. This function wraps an input object using a Proxy
to intercept and control interactions with the object's properties. Follow these details to grasp the workings of the code:
- Define a
Set
calledmutatingMethods
containing method names that typically modify an object, such as 'pop', 'push', 'shift', 'unshift', 'splice', 'sort', and 'reverse'. - Construct a
proxyHandler
object to manage access and modifications to the target object:- set: This trap prevents any changes to properties of the target object. It throws an error when an attempt is made to set a property, with specific error messages depending on whether the target is an array or not.
- get: This trap retrieves properties from the target. If the property value is not an object or function (or is null), it returns the value directly. Otherwise, it returns a new
Proxy
of the value, using the same handler to ensure nested objects are also immutable. - apply: If a function that belongs to the
mutatingMethods
set is called, this trap throws an error preventing the operation. Otherwise, it allows the function to execute normally usingReflect.apply
.
- Finally, the
createImmutableObject
function returns a newProxy
created with the input object and the definedproxyHandler
, effectively making the input object immutable.
Use this implementation to safeguard objects against unwanted modifications, ensuring data integrity and consistency where mutations could lead to bugs or unexpected behavior.
No comments yet.