Hone logo
Hone
Problems

Advanced Generic Constraints: Building a Type-Safe Data Processor

This challenge focuses on creating a flexible and robust data processing function in TypeScript using advanced generic constraints. You will build a processData function that can handle various data structures (arrays, objects) and apply transformations based on specific conditions, ensuring type safety throughout the process. This is useful for creating reusable utility functions that adapt to different data types without sacrificing compile-time checks.

Problem Description

Your task is to implement a TypeScript function named processData. This function should accept two arguments:

  1. data: The data to be processed. This can be an array of items or an object where keys are strings and values are of a consistent type.
  2. processor: A function that takes a single item from the data and returns a processed version of that item.

The processData function should dynamically determine the type of data (array or object) and apply the processor function accordingly.

Key Requirements:

  • Generics: The function must be generic to handle various input data types and their corresponding processed output types.
  • Conditional Type for Data Structure: Implement logic to differentiate between array and object inputs for data.
  • Type-Safe Processing: Ensure that the processor function is applied correctly to elements of an array or values of an object.
  • Return Type: The processData function should return a new data structure of the same shape as the input data but with the processed values.

Expected Behavior:

  • If data is an array, processData should return a new array where each element is the result of applying processor to the corresponding element of the input array.
  • If data is an object, processData should return a new object with the same keys, where each value is the result of applying processor to the corresponding value of the input object.

Edge Cases to Consider:

  • Empty arrays and empty objects.

Examples

Example 1: Processing an array of numbers.

const numbers = [1, 2, 3, 4];
const double = (n: number): number => n * 2;

const processedNumbers = processData(numbers, double);
// processedNumbers should be [2, 4, 6, 8]
// The type of processedNumbers should be number[]

Example 2: Processing an object of strings.

const user = {
  firstName: "John",
  lastName: "Doe",
};
const capitalize = (s: string): string => s.toUpperCase();

const processedUser = processData(user, capitalize);
// processedUser should be { firstName: "JOHN", lastName: "DOE" }
// The type of processedUser should be { firstName: string; lastName: string; }

Example 3: Processing an array of objects.

interface Product {
  id: number;
  name: string;
  price: number;
}

const products: Product[] = [
  { id: 1, name: "Laptop", price: 1200 },
  { id: 2, name: "Mouse", price: 25 },
];

const applyDiscount = (p: Product): Product => ({
  ...p,
  price: p.price * 0.9, // 10% discount
});

const discountedProducts = processData(products, applyDiscount);
// discountedProducts should be an array of Product objects with discounted prices.
// The type of discountedProducts should be Product[]

Constraints

  • The data input will either be an array or a plain JavaScript object with string keys.
  • The processor function will always accept a single argument and return a single value.
  • The function should handle types correctly, ensuring that the output type reflects the transformation applied by the processor.

Notes

Consider using TypeScript's conditional types and mapped types to create a sophisticated and type-safe processData function. Think about how you can infer the types of the input data and the output of the processor to construct the correct return type. You might want to define type helpers to discriminate between array and object types.

Loading editor...
typescript