Hone logo
Hone
Problems

TypeScript Readonly Deep Modifier

This challenge focuses on implementing a TypeScript utility type that recursively makes all properties of a given type, including nested objects and arrays, readonly. This is crucial for ensuring data immutability, preventing accidental modifications in complex data structures, and improving code safety.

Problem Description

You need to create a TypeScript generic utility type called DeepReadonly<T>. This type should take a type T and return a new type where:

  1. All properties of T are marked as readonly.
  2. If a property's value is an object (but not null), its properties should also be recursively marked as readonly using DeepReadonly.
  3. If a property's value is an array, the array itself should be marked as readonly, and its elements should also be recursively marked as readonly using DeepReadonly.

Key Requirements:

  • The DeepReadonly<T> type must handle primitive types, objects, arrays, and null values correctly.
  • The recursion should apply to nested objects and array elements.
  • The resulting type should effectively prevent any mutation of the original data structure.

Expected Behavior:

Given an input type, DeepReadonly<T> should produce a type where all properties are readonly, and any nested objects or array elements are also deeply readonly.

Edge Cases to Consider:

  • null values: null should remain null and not be treated as an object that needs deeper readonly conversion.
  • Arrays of primitives: The array itself should be readonly, and the primitive elements within should also be treated as readonly (though primitives are inherently immutable).
  • Arrays of objects: Both the array and the objects within the array should be deeply readonly.
  • Readonly properties in the original type: These should remain readonly.
  • Optional properties: These should still be optional but readonly.

Examples

Example 1:

Input Type: {
  name: string;
  age: number;
  address: {
    street: string;
    city: string;
  };
}

Output Type: {
  readonly name: string;
  readonly age: number;
  readonly address: {
    readonly street: string;
    readonly city: string;
  };
}

Explanation: All properties, including those in the nested 'address' object, are made readonly.

Example 2:

Input Type: {
  items: Array<{ id: number; value: string }>;
  isActive: boolean;
}

Output Type: {
  readonly items: ReadonlyArray<{ readonly id: number; readonly value: string }>;
  readonly isActive: boolean;
}

Explanation: The 'items' array is marked as ReadonlyArray, and its object elements are also deeply readonly. 'isActive' is a primitive and becomes readonly.

Example 3:

Input Type: {
  data: {
    users: Array<{ name: string; settings?: { theme: string } }>;
    config: null | { retries: number };
  };
  version: 1;
}

Output Type: {
  readonly data: {
    readonly users: ReadonlyArray<{ readonly name: string; readonly settings?: { readonly theme: string } }>;
    readonly config: null | { readonly retries: number };
  };
  readonly version: 1;
}

Explanation: This example demonstrates handling optional properties within arrays, null values, and primitive union types.

Constraints

  • The solution must be a single generic TypeScript utility type DeepReadonly<T>.
  • The type must be fully implemented using TypeScript's type system. No runtime JavaScript code is expected.
  • The solution should be efficient in terms of type checking complexity.

Notes

  • Consider how to distinguish between objects that should be recursed into and other types like null or primitives.
  • The ReadonlyArray<T> built-in utility type will be useful for handling arrays.
  • Think about how to handle union types, especially those involving null.
  • The goal is to create a type that provides compile-time immutability guarantees.
Loading editor...
typescript