Back

Advanced Generic Types in TypeScript

Generics enable you to create flexible, reusable functions and types that work with multiple data types while maintaining type safety. This section explores advanced techniques combining generics with utility types and constraints.

By leveraging generics with keyof and utility types such as Pick and Exclude, you can build powerful utilities to manipulate object types dynamically, ensuring your code stays robust and maintainable.

Getting a Property Value with Generics

const getValue = <T extends object,>(obj: T, property: keyof T): T[keyof T] => {
    return obj[property];
  }
  
  const person = { name: "Alice", age: 30, active: true };
  console.log(getValue(person, 'age')); // Output: 30
  console.log(getValue(person, 'name')); // Output: Alice

This function takes an object and a key, then returns the corresponding property value with full type safety guaranteed by TypeScript's generic constraints.

Omitting a Single Property from an Object

type MyOmit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
  
  // Remove a single property from object type and value
  const deleteValue = <T extends object, K extends keyof T>(obj: T, property: K): MyOmit<T, K> => {
    const { [property]: _, ...rest } = obj;
    return rest;
  };
  
  const obj = { name: "Alice", age: 30, manager: "Bob" };
  const withoutManager = deleteValue(obj, "manager");
  console.log(withoutManager); // Output: { name: "Alice", age: 30 }

Using a custom MyOmit type, we create a utility function that returns a new object without the specified property, reflecting the type change in the return value.

Omitting Multiple Properties from an Object

// Remove multiple properties from object
  const deleteValues = <T extends object, K extends (keyof T)[]>(obj: T, properties: K): MyOmit<T, K[number]> => {
    const result = { ...obj };
    properties.forEach(prop => {
      delete result[prop];
    });
    return result;
  };
  
  const originalObj = { name: "Alice", age: 30, manager: "Bob", active: true };
  const reducedObj = deleteValues(originalObj, ['name', 'age']);
  console.log(reducedObj); // Output: { manager: "Bob", active: true }

Extending the previous example, we now remove multiple keys from an object by passing an array of keys. The return type dynamically excludes all specified keys.

Updating a Property Value Safely

// Example: Generic function to update a property value safely
  const updateValue = <T extends object, K extends keyof T>(obj: T, key: K, value: T[K]): T => {
    return { ...obj, [key]: value };
  };
  
  const user = { id: 1, username: "user1", isAdmin: false };
  const updatedUser = updateValue(user, "isAdmin", true);
  console.log(updatedUser); // Output: { id: 1, username: "user1", isAdmin: true }

A generic function to update a given property on an object, ensuring the new value matches the property's type.

Mapping Over Object Keys and Values

// Generic function to map over object keys and values
  function mapObject<T extends object, R>(obj: T, fn: (key: keyof T, value: T[keyof T]) => R): R[] {
    return (Object.keys(obj) as (keyof T)[]).map(key => fn(key, obj[key]));
  }
  
  const data = { a: 1, b: 2, c: 3 };
  const result = mapObject(data, (key, value) => `${key}: ${value * 2}`);
  console.log(result); // Output: ["a: 2", "b: 4", "c: 6"]

This utility function uses generics to map over an object's keys and values, applying a callback to each pair, and returns the mapped results as an array.

Back