Hone logo
Hone
Problems

Deeply Readonly Helper in TypeScript

Creating immutable data structures is crucial for predictable state management and debugging in many applications. This challenge asks you to implement a utility function that recursively makes all properties of an object, including nested objects and arrays, readonly in TypeScript. This is particularly useful when dealing with complex data structures where you want to prevent accidental modification.

Problem Description

You need to implement a function called deepReadonly that takes an object or array as input and returns a new object or array where all properties (including those within nested objects and arrays) are readonly. The original object/array should not be modified. The function should handle primitive types, objects, arrays, and null/undefined values correctly.

Key Requirements:

  • Recursion: The function must recursively traverse the input object/array to ensure all nested properties are also readonly.
  • Immutability: The original input object/array must remain unchanged. A new object/array with readonly properties must be returned.
  • Handles Primitives: Primitive types (string, number, boolean, symbol, bigint, null, undefined) should be left untouched.
  • Handles Arrays: Arrays should also have their elements made readonly.
  • Handles Null/Undefined: Null and undefined values should be handled gracefully and returned as is (without attempting to make them readonly).

Expected Behavior:

The function should return a new object/array with all properties marked as readonly. Attempting to modify a property on the returned object/array should result in a TypeScript compile-time error (or a runtime error if the code is not properly type-checked).

Edge Cases to Consider:

  • Circular references: The function should avoid infinite loops if the input object contains circular references. (While a full circular reference detection is beyond the scope of this challenge, the function should not crash).
  • Functions: Functions should be left untouched.
  • Symbols: Symbols should be left untouched.
  • Dates: Dates should be treated as objects and their properties made readonly.

Examples

Example 1:

Input: { name: "John", age: 30, address: { street: "123 Main St", city: "Anytown" } }
Output: { readonly name: "John"; readonly age: 30; readonly address: { readonly street: "123 Main St"; readonly city: "Anytown" } }
Explanation: The input object's properties 'name', 'age', and 'address' are made readonly. The nested 'address' object's properties 'street' and 'city' are also made readonly.

Example 2:

Input: [1, 2, { a: 1, b: 2 }, [3, 4]]
Output: [readonly 1, readonly 2, readonly { readonly a: 1; readonly b: 2 }, readonly [readonly 3, readonly 4]]
Explanation: The array's elements are made readonly. The nested object and array are also recursively made readonly.

Example 3: (Edge Case - Circular Reference)

Input: let obj: any = { a: 1 }; obj.b = obj;  obj
Output: { readonly a: 1; readonly b: { a: 1 } }  (The function should not crash, but may not fully resolve the circular reference)
Explanation: The function should attempt to process the object, but avoid an infinite loop due to the circular reference.  The presence of the circular reference might lead to unexpected behavior, but the primary goal is to prevent a crash.

Constraints

  • The function must be written in TypeScript.
  • The function should handle objects and arrays of arbitrary depth.
  • The function should not modify the original input object/array.
  • Performance: While not a primary concern, the function should be reasonably efficient. Avoid unnecessary iterations or allocations.
  • Input: The input can be any valid JavaScript object or array.

Notes

  • Consider using the Object.keys() method to iterate over the properties of an object.
  • You can use the in operator to check if a property exists on an object.
  • The Readonly<T> utility type can be used to make a type readonly.
  • Think about how to handle different data types (primitive, object, array, null, undefined) appropriately.
  • A recursive approach is essential for handling nested objects and arrays.
  • Be mindful of potential circular references and how to avoid infinite loops.
Loading editor...
typescript