We use cookies (including Google cookies) to personalize ads and analyze traffic. By continuing to use our site, you accept our Privacy Policy.

Make Object Immutable

Number: 2776

Difficulty: Medium

Paid? Yes

Companies: N/A


Problem Description

Write a function that takes a valid JSON object or array and returns a new immutable version of that object. This new object should throw a string error when any mutation is attempted. The error messages must follow these rules:

  • Setting or deleting a key on an object must throw "Error Modifying: {key}".
  • Setting or deleting an index on an array must throw "Error Modifying Index: {index}".
  • Calling a mutating array method (one of [pop, push, shift, unshift, splice, sort, reverse]) must throw "Error Calling Method: {methodName}".

Key Insights

  • Use Proxies (in languages that support them, e.g. JavaScript) or wrapper classes to intercept modifications.
  • Recursively wrap nested objects or arrays to ensure deep immutability.
  • Distinguish between object keys and array indices during property modifications.
  • Intercept calls to mutating array methods and return a wrapped function that always throws the appropriate error.

Space and Time Complexity

Time Complexity: O(n) where n is the total number of properties or elements in the object. Each element is processed once during the wrapping. Space Complexity: O(n) due to the recursive wrappers and the proxy objects for each nested element.


Solution

The solution involves recursively wrapping the target object or array with a Proxy (or with custom wrapper classes in languages that lack Proxy support). The Proxy traps the following operations:

  • The get trap inspects if the accessed property is a function from a list of mutating methods (for arrays). If it is, a wrapped function that always throws the appropriate error is returned.
  • The set and deleteProperty traps immediately throw an error if any attempt is made to modify a property. They differentiate between objects and arrays (checking if the property represents an array index). This approach guarantees that any attempt to modify the returned object, whether on the top level or nested, will produce the required error.

Code Solutions

def makeImmutable(obj):
    # For Python, we create a custom wrapper class.
    mutating_methods = {'pop', 'push', 'shift', 'unshift', 'splice', 'sort', 'reverse'}
    
    # Helper function to check if a key represents an integer index in a list.
    def is_int_index(key):
        try:
            i = int(key)
            return str(i) == str(key)
        except:
            return False

    class ImmutableWrapper:
        def __init__(self, data):
            self._data = data
        
        def __getitem__(self, key):
            value = self._data[key]
            return makeImmutable(value) if isinstance(value, (dict,list)) else value
        
        def __setitem__(self, key, value):
            if isinstance(self._data, list) and isinstance(key, int):
                raise Exception(f"Error Modifying Index: {key}")
            else:
                raise Exception(f"Error Modifying: {key}")
        
        def __delitem__(self, key):
            if isinstance(self._data, list) and isinstance(key, int):
                raise Exception(f"Error Modifying Index: {key}")
            else:
                raise Exception(f"Error Modifying: {key}")
        
        def __getattr__(self, name):
            # Allow read-only access only.
            attr = getattr(self._data, name)
            if callable(attr) and name in mutating_methods:
                def method_wrapper(*args, **kwargs):
                    raise Exception(f"Error Calling Method: {name}")
                return method_wrapper
            return attr
        
        def __iter__(self):
            return iter(self._data)
        
        def __len__(self):
            return len(self._data)
        
        def __repr__(self):
            return repr(self._data)
    
    if isinstance(obj, (dict, list)):
        return ImmutableWrapper(obj)
    return obj

# Example usage:
obj = {"x": 5}
immutable_obj = makeImmutable(obj)
try:
    immutable_obj["x"] = 10
except Exception as e:
    print({"value": None, "error": str(e)})
← Back to All Questions