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:
-
remapKeys<T extends Record<string, any>, K extends string>(obj: T, keyMap: Record<string, string>): T- This function takes an object
objof typeTand akeyMapwhich 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
objis not present inkeyMap, it should be omitted from the new object. - The type
Tshould be preserved as much as possible, ensuring type safety.
- This function takes an object
-
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,
deepRemapKeysshould be called on that nested object. - Primitive values (string, number, boolean, null, undefined) should not be remapped.
- This function is similar to
-
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 aPartial<T>object. This means that keys not found in thekeyMapare 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.
- This function is similar to
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
keyMapwill always be a validRecord<string, string>. - The input object
objcan be of any shape, including nested objects. - The keys in the
keyMapwill 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.