Hone logo
Hone
Problems

Key Remapping Utilities in TypeScript

This challenge focuses on building reusable TypeScript utilities for remapping keys in JavaScript objects. Key remapping is a common task when migrating codebases, adapting to different APIs, or simply refactoring to improve code clarity. Your goal is to implement functions that safely and efficiently remap keys in objects, handling various scenarios including optional keys and nested objects.

Problem Description

You are tasked with creating a set of TypeScript functions to remap keys in JavaScript objects. These functions should be robust, type-safe, and handle various edge cases gracefully. The core functionality revolves around providing a mapping of old keys to new keys and applying this mapping to an object.

Specifically, you need to implement the following functions:

  1. remapKeys<T extends Record<string, any>, K extends string>(obj: T, keyMap: Record<string, string>): T

    • This function takes an object obj of type T and a keyMap which is a mapping of old keys (strings) to new keys (strings).
    • It returns a new object with the keys remapped according to the keyMap.
    • If a key in obj is not present in keyMap, it should be omitted from the new object.
    • The type T should be preserved as much as possible, ensuring type safety.
  2. deepRemapKeys<T extends Record<string, any>, K extends string>(obj: T, keyMap: Record<string, string>): T

    • This function is similar to remapKeys, but it recursively applies the key remapping to nested objects within the input object.
    • If a value in the object is itself an object, deepRemapKeys should be called on that nested object.
    • Primitive values (string, number, boolean, null, undefined) should not be remapped.
  3. optionalRemapKeys<T extends Record<string, any>, K extends string>(obj: T, keyMap: Record<string, string>): Partial<T>

    • This function is similar to remapKeys, but it returns a Partial<T> object. This means that keys not found in the keyMap are not only omitted but also treated as optional in the returned object. This is useful when dealing with APIs where certain keys might be optional.

Examples

Example 1:

Input:
obj: { a: 1, b: 2, c: 3 }
keyMap: { a: 'x', b: 'y' }
Output: { x: 1, y: 2 }
Explanation: Keys 'a' and 'b' are remapped to 'x' and 'y' respectively. Key 'c' is omitted because it's not in the keyMap.

Example 2:

Input:
obj: { a: 1, b: { c: 2, d: 3 }, e: 4 }
keyMap: { a: 'x', c: 'y', e: 'z' }
Output: { x: 1, b: { y: 2, d: 3 }, z: 4 }
Explanation: 'a' is remapped to 'x', 'e' is remapped to 'z'.  The nested object's 'c' is remapped to 'y'. 'd' is preserved.

Example 3:

Input:
obj: { a: 1, b: 2, c: 3 }
keyMap: { a: 'x', b: 'y' }
Output: { x?: 1, y?: 2 }
Explanation: Keys 'a' and 'b' are remapped to 'x' and 'y' respectively. Key 'c' is omitted, and the returned object is marked as `Partial<T>`.

Constraints

  • The keyMap will always be a valid Record<string, string>.
  • The input object obj can be of any shape, including nested objects.
  • The keys in the keyMap will always be strings.
  • The performance of the functions should be reasonable for objects of moderate size (up to 1000 keys). Avoid unnecessary iterations or allocations.
  • Type safety is paramount. The functions should leverage TypeScript's type system to ensure correctness.

Notes

  • Consider using TypeScript's utility types like Partial, Record, and generics to create type-safe and reusable functions.
  • Think about how to handle circular references in nested objects when implementing deepRemapKeys. While a full solution for circular references is not required, be aware of the potential issue. A simple check for the same object reference can prevent infinite recursion.
  • Focus on creating clean, readable, and well-documented code.
  • The functions should return a new object, not modify the original object in place. Immutability is important.
Loading editor...
typescript