Type-safe pick and omit
typescriptProduction-ready pick and omit utility functions that leverage TypeScript's type system to create perfectly typed object subsets with full inference support.
Code
/**
* Picks specified keys from an object with full type inference.
* Returns a new object containing only the specified properties.
*/
function pick<T extends object, K extends keyof T>(
obj: T,
keys: readonly K[]
): Pick<T, K> {
const result = {} as Pick<T, K>;
for (const key of keys) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
result[key] = obj[key];
}
}
return result;
}
/**
* Omits specified keys from an object with full type inference.
* Returns a new object excluding the specified properties.
*/
function omit<T extends object, K extends keyof T>(
obj: T,
keys: readonly K[]
): Omit<T, K> {
const result = { ...obj };
for (const key of keys) {
delete (result as Record<string, unknown>)[key as string];
}
return result as Omit<T, K>;
}
// ===== Usage Examples =====
interface User {
id: number;
name: string;
email: string;
password: string;
createdAt: Date;
}
const user: User = {
id: 1,
name: "Alice",
email: "alice@example.com",
password: "secret123",
createdAt: new Date()
};
// Pick specific fields - result is typed as Pick<User, "id" | "name">
const publicProfile = pick(user, ["id", "name"] as const);
console.log(publicProfile); // { id: 1, name: "Alice" }
// publicProfile.email; // TypeScript error: Property 'email' does not exist
// Omit sensitive fields - result is typed as Omit<User, "password">
const safeUser = omit(user, ["password"] as const);
console.log(safeUser); // { id: 1, name: "Alice", email: "alice@example.com", createdAt: Date }
// safeUser.password; // TypeScript error: Property 'password' does not exist
// Type inference works with literal arrays using 'as const'
const keys = ["id", "email"] as const;
const contact = pick(user, keys);
// contact is Pick<User, "id" | "email">
// Chaining operations
const minimalUser = omit(pick(user, ["id", "name", "email"] as const), ["email"] as const);
console.log(minimalUser); // { id: 1, name: "Alice" }
// Works with optional properties
interface Config {
host: string;
port?: number;
debug?: boolean;
}
const config: Config = { host: "localhost", port: 3000 };
const hostOnly = pick(config, ["host"] as const);
console.log(hostOnly); // { host: "localhost" }
export { pick, omit };How It Works
These utility functions solve a common problem in TypeScript: creating object subsets while maintaining complete type safety. Unlike lodash's pick and omit which return loosely typed objects, these implementations leverage TypeScript's mapped types (Pick<T, K> and Omit<T, K>) to ensure the returned objects have exactly the right type.
The generic constraints are crucial to how these functions work. The T extends object constraint ensures we're working with objects, while K extends keyof T guarantees that only valid keys of the input object can be specified. This means TypeScript will catch typos and invalid property names at compile time, not runtime. The readonly K[] parameter type allows both mutable arrays and readonly tuples (created with as const), which is essential for type inference.
The as const assertion on the keys array is a key detail that developers often miss. Without it, TypeScript infers the array type as string[] rather than a tuple of literal types, which loses the specific key information needed for accurate return types. Using as const tells TypeScript to infer the narrowest possible type, preserving the exact key literals.
The pick implementation creates a new empty object and copies only the specified properties, using Object.prototype.hasOwnProperty.call() for safety against objects with null prototypes or overridden hasOwnProperty methods. The omit implementation takes the opposite approach: it spreads the entire object first, then deletes the unwanted keys. The type assertion on the delete operation is necessary because TypeScript's type narrowing doesn't track property deletion.
Use these utilities when you need to create API responses that exclude sensitive fields, prepare objects for serialization, or extract specific properties for component props. Avoid them in hot paths where object creation overhead matters, or when working with objects that have symbol keys (these implementations only handle string keys). For very large objects where you're omitting most properties, pick might be more performant than omit since it only copies what's needed.