Hone logo
Hone
Problems

Utility Types with extends in TypeScript: Deeply Checking and Transforming Object Structures

This challenge focuses on leveraging TypeScript's utility types, particularly extends, to create reusable functions that analyze and manipulate object structures. Understanding these utilities is crucial for writing type-safe and maintainable code, especially when dealing with complex data models. You'll be building functions that inspect object types and perform transformations based on their properties.

Problem Description

You are tasked with implementing three utility type functions in TypeScript using extends and other utility types. These functions will help analyze and modify object types based on their structure.

  1. PickByType: This function takes an object type T and a type U as input. It returns a new type that picks only the properties from T whose types extends U. Essentially, it selects properties whose types are a subtype of U.

  2. PartialByKeys: This function takes an object type T and an array of keys K (where K is a subset of the keys of T) as input. It returns a new type where only the properties specified in K are made optional. The rest of the properties in T remain required.

  3. MergeOptional: This function takes two object types, T and U, and merges them into a single object type. If a property exists in both T and U, the resulting type uses the union of their types. If a property exists only in one of the types, it's included as is. Crucially, if a property is optional in either T or U, it must be optional in the merged type.

Examples

Example 1: PickByType

type InputType = {
  name: string;
  age: number;
  isActive: boolean;
  address: {
    street: string;
    city: string;
  };
};

type StringType = string;

type Result = PickByType<InputType, StringType>;
// Expected Output: { name: string; city: string; }
// Explanation: 'name' and 'city' are the only properties whose types extend 'string'.

Example 2: PartialByKeys

type InputType = {
  name: string;
  age: number;
  isActive: boolean;
};

type KeysToPartial = ['name', 'age'];

type Result = PartialByKeys<InputType, KeysToPartial>;
// Expected Output: { name?: string; age?: number; isActive: boolean; }
// Explanation: 'name' and 'age' are now optional, while 'isActive' remains required.

Example 3: MergeOptional

type TypeA = {
  name: string;
  age?: number;
  isActive: boolean;
};

type TypeB = {
  name: string;
  city: string;
  age: number;
};

type Result = MergeOptional<TypeA, TypeB>;
// Expected Output: { name: string; age?: number | number; city: string; isActive: boolean; }
// Explanation: 'name' is merged (string), 'age' is merged (number | number which simplifies to number), 'city' is added, and 'isActive' is added.

Constraints

  • All functions must be implemented using TypeScript's type system and utility types.
  • The functions should be generic and work with any valid object types.
  • The KeysToPartial array in PartialByKeys must contain keys that actually exist in the input object type T. (While you don't need to enforce this at runtime, the type system should ideally catch errors if this isn't the case).
  • The MergeOptional function should correctly handle optional properties from both input types, ensuring the merged type reflects the optionality.
  • The solutions should be concise and readable.

Notes

  • Consider using conditional types (infer, extends) extensively to achieve the desired type manipulations.
  • Think about how to handle potential type intersections and unions effectively.
  • The goal is to create type-level functions, not runtime functions. You are defining types, not JavaScript code.
  • Focus on the type definitions themselves; no runtime code is required.
  • Pay close attention to how optionality is handled in MergeOptional. This is a key aspect of the challenge.
Loading editor...
typescript