When transforming arrays with map, it’s common to need only specific properties rather than the entire object. Writing this inline can be verbose, but defining a property extractor function upfront enables concise expressions like map(propsExtractor('id', 'name')).
Code
type LiteralObject = Record<string, unknown>
/** * Generate a function that extracts only specified properties from an object */export function propsExtractor< Props extends LiteralObject, PropKey extends keyof Props,>( ...props: PropKey[]): (obj: Props) => Pick<Props, PropKey> { return (obj: Props) => { const result: Partial<Props> = {} for (const prop of props) { if (Object.hasOwn(obj, prop)) { result[prop] = obj[prop] } } return result as Pick<Props, PropKey> }}Usage
type User = { id: string name: string email: string role: string createdAt: Date}
const users: User[] = [ { id: '1', name: 'Alice', email: 'alice@example.com', role: 'admin', createdAt: new Date() }, { id: '2', name: 'Bob', email: 'bob@example.com', role: 'user', createdAt: new Date() },]
// Extract only ID and nameconst userSummaries = users.map(propsExtractor('id', 'name'))// => [{ id: '1', name: 'Alice' }, { id: '2', name: 'Bob' }]
// Works cleanly even in nested mapsconst departments = [ { name: 'Engineering', members: users }, { name: 'Sales', members: users },]
const summaries = departments.map(dept => ({ department: dept.name, members: dept.members.map(propsExtractor('id', 'name'))}))How It Works
propsExtractoraccepts property names to extract as variadic arguments- Returns a function of type
(obj: Props) => Pick<Props, PropKey>as a closure - The returned function checks property existence with
Object.hasOwnbefore extracting - TypeScript’s
Pickutility type ensures accurate type inference for the return value
Using Object.hasOwn ensures safety by not mistakenly extracting properties from the prototype chain.
Benefits
- Readable: Intent is clear with expressions like
map(propsExtractor('id', 'name')) - Type-safe: TypeScript type inference accurately determines the extracted type
- Reusable: Define once, use in multiple locations
- Handles nesting well: Maintains readability even with multiple
mapByapplications
Caveats
Properties that don’t exist are not included in the result (not even as undefined). If you want to include all properties, remove the Object.hasOwn check. Also, if extracting many properties, explicitly defining an interface may be more maintainable.
Applications
- Extract only necessary fields from API responses before returning to clients
- Exclude properties containing sensitive information during logging (create an inverse filter version)
- Extract partial state in Redux or Zustand selectors
- Implement partial retrieval logic similar to GraphQL field selection
hsb.horse