transformKeys
Transforms object keys while preserving values and structure. Creates a new object by applying a transformation function to each key while keeping all values unchanged. Useful for key normalization, naming convention changes, and object schema transformations. Maintains type safety through generic constraints and preserves object relationships.
Signature
const transformKeys: <Type extends Record<PropertyKey, any>, Key extends PropertyKey>(object: Type, getKey: (value: Type[keyof Type], key: keyof Type, object: Type) => Key) => Record<Key, Type[keyof Type]>
Parameters
| Name | Type | Description |
|---|---|---|
object | - | Object whose keys will be transformed |
getKey | - | Function that transforms each key, receives (value, key, object) |
Returns
New object with transformed keys and original values
Examples
Basic key transformation
import { transformKeys } from '@winglet/common-utils';
// Add prefix to all keys
const data = { name: 'John', age: 30, city: 'NYC' };
const prefixed = transformKeys(data, (_, key) => `user_${key}`);
console.log(prefixed); // { user_name: 'John', user_age: 30, user_city: 'NYC' }
// Convert to uppercase
const uppercase = transformKeys(data, (_, key) => key.toUpperCase());
console.log(uppercase); // { NAME: 'John', AGE: 30, CITY: 'NYC' }
Case conversion and normalization
// Snake case to camel case
const snakeCase = {
user_name: 'Alice',
first_name: 'Alice',
last_name: 'Smith',
email_address: 'alice@example.com'
};
const camelCase = transformKeys(snakeCase, (_, key) =>
key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase())
);
console.log(camelCase);
// { userName: 'Alice', firstName: 'Alice', lastName: 'Smith', emailAddress: 'alice@example.com' }
// Camel case to kebab case
const kebabCase = transformKeys(camelCase, (_, key) =>
key.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`)
);
console.log(kebabCase);
// { 'user-name': 'Alice', 'first-name': 'Alice', 'last-name': 'Smith', 'email-address': 'alice@example.com' }
Value-dependent key transformation
// Transform keys based on their values
const config = {
debug: true,
timeout: 5000,
retries: 3,
enabled: false
};
// Add type suffix based on value type
const typed = transformKeys(config, (value, key) => {
const type = typeof value;
return `${key}_${type}`;
});
console.log(typed);
// { debug_boolean: true, timeout_number: 5000, retries_number: 3, enabled_boolean: false }
// Prefix based on value
const prefixed = transformKeys(config, (value, key) =>
value === true ? `enabled_${key}` :
value === false ? `disabled_${key}` :
`config_${key}`
);
console.log(prefixed);
// { enabled_debug: true, config_timeout: 5000, config_retries: 3, disabled_enabled: false }
API response transformation
// Transform API response keys to match frontend naming
const apiResponse = {
user_id: 123,
user_name: 'john_doe',
created_at: '2023-01-01T00:00:00Z',
updated_at: '2023-01-02T12:00:00Z',
is_active: true,
profile_image_url: 'https://example.com/image.jpg'
};
const frontendFormat = transformKeys(apiResponse, (_, key) => {
// Convert snake_case to camelCase
return key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
});
console.log(frontendFormat);
// {
// userId: 123,
// userName: 'john_doe',
// createdAt: '2023-01-01T00:00:00Z',
// updatedAt: '2023-01-02T12:00:00Z',
// isActive: true,
// profileImageUrl: 'https://example.com/image.jpg'
// }
Database column mapping
// Map database columns to domain model properties
const dbRecord = {
id: 1,
usr_nm: 'alice',
eml_addr: 'alice@example.com',
crt_dt: '2023-01-01',
updt_dt: '2023-01-02'
};
const columnMapping = {
id: 'id',
usr_nm: 'username',
eml_addr: 'email',
crt_dt: 'createdDate',
updt_dt: 'updatedDate'
};
const domainModel = transformKeys(dbRecord, (_, key) =>
columnMapping[key] || key
);
console.log(domainModel);
// {
// id: 1,
// username: 'alice',
// email: 'alice@example.com',
// createdDate: '2023-01-01',
// updatedDate: '2023-01-02'
// }
Advanced transformation with context
// Use full object context for complex transformations
const userProfile = {
name: 'John',
age: 30,
role: 'admin',
permissions: ['read', 'write', 'delete']
};
// Add role-based prefixes
const roleBasedKeys = transformKeys(userProfile, (value, key, obj) => {
const rolePrefix = obj.role === 'admin' ? 'admin_' : 'user_';
return key === 'role' ? key : `${rolePrefix}${key}`;
});
console.log(roleBasedKeys);
// {
// admin_name: 'John',
// admin_age: 30,
// role: 'admin',
// admin_permissions: ['read', 'write', 'delete']
// }
Key sanitization and validation
// Sanitize keys for safe property access
const unsafeKeys = {
'user name': 'John',
'user-age': 30,
'user.email': 'john@example.com',
'123invalid': 'value',
'valid_key': 'good'
};
const sanitized = transformKeys(unsafeKeys, (_, key) => {
// Replace invalid characters and ensure valid identifier
let clean = key.replace(/[^a-zA-Z0-9_]/g, '_');
// Ensure it doesn't start with a number
if (/^\d/.test(clean)) {
clean = `key_${clean}`;
}
return clean;
});
console.log(sanitized);
// {
// user_name: 'John',
// user_age: 30,
// user_email: 'john@example.com',
// key_123invalid: 'value',
// valid_key: 'good'
// }
Playground
import { transformKeys } from '@winglet/common-utils'; // Add prefix to all keys const data = { name: 'John', age: 30, city: 'NYC' }; const prefixed = transformKeys(data, (_, key) => `user_${key}`); console.log(prefixed); // { user_name: 'John', user_age: 30, user_city: 'NYC' } // Convert to uppercase const uppercase = transformKeys(data, (_, key) => key.toUpperCase()); console.log(uppercase); // { NAME: 'John', AGE: 30, CITY: 'NYC' }
Notes
Transformation Process:
- Iterates through all enumerable own properties using Object.keys()
- Calls transformation function for each property with (value, key, object)
- Creates new object with transformed keys and original values
- Maintains property enumeration order
Performance Characteristics:
- Time Complexity: O(n) where n is number of properties
- Space Complexity: O(n) for the new object
- Memory: Creates entirely new object, doesn't modify original
Type Safety:
- Input object type is preserved for value access
- Output key type is inferred from transformation function return
- Values maintain their original types
- Provides full TypeScript support with generics
TypeScript Limitations and Workarounds:
// Limitation: Key type inference
const obj = { a: 1, b: 2 };
const result = transformKeys(obj, (_, key) => key.toUpperCase());
// Result type: Record<string, number> (loses specific key info)
// Workaround: Explicit typing
const betterResult = transformKeys(obj, (_, key) => key.toUpperCase()) as Record<'A' | 'B', number>;
// Limitation: Dynamic key generation
const dynamic = transformKeys(obj, (_, key) => Math.random() > 0.5 ? key : 'default');
// TypeScript can't track conditional key logic
// Limitation: Non-string key return
const symbolKeys = transformKeys(obj, () => Symbol('key'));
// Works at runtime but type checking may be limited
Use Cases:
- API response/request format conversion
- Database column to domain model mapping
- Naming convention standardization
- Key sanitization and validation
- Schema transformation and migration
- Internationalization key mapping
Limitations:
- Only processes enumerable own properties
- Doesn't handle Symbol keys
- Key collisions after transformation may overwrite values
- Circular references in transformation logic may cause issues
- Performance impact grows linearly with object size