Hone logo
Hone
Problems

Structural Typing Helpers in TypeScript

Structural typing in TypeScript allows you to check if an object conforms to a certain shape without explicitly declaring inheritance. This challenge asks you to implement helper functions that facilitate working with structural typing, particularly when dealing with optional properties and complex type definitions. These helpers will improve code readability and reduce boilerplate when validating objects against specific structural types.

Problem Description

You are tasked with creating three TypeScript functions that aid in structural typing validation. These functions will help determine if an object satisfies a given type definition, handling optional properties gracefully.

  1. isAssignable<T>(obj: any): obj is T: This function takes an object obj of type any and checks if it is assignable to a type T. It should return a boolean indicating whether the object conforms to the structure of type T. Crucially, it must use a type predicate, returning obj is T.

  2. deepPartial<T>(obj: T): DeepPartial<T>: This function takes an object obj of type T and returns a new object where all properties of T are optional. This is useful for creating partial updates or allowing for incomplete data. The return type DeepPartial<T> is a utility type that recursively makes all properties optional, including nested objects.

  3. pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K>: This function takes an object obj of type T and an array of keys keys (where each key is a property of T). It returns a new object containing only the properties specified in the keys array, preserving their original types. The return type Pick<T, K> is a utility type that creates a new type by picking a set of properties K from T.

Examples

Example 1:

interface Person {
  name: string;
  age: number;
  address?: string;
}

const person1: Person = { name: "Alice", age: 30 };
const person2: any = { name: "Bob", age: 25, address: "123 Main St" };

// isAssignable
isAssignable<Person>(person1); // true
isAssignable<Person>(person2); // true (because address is optional)
isAssignable<Partial<Person>>(person1); // false (name and age are required in Partial<Person>)

// deepPartial
const partialPerson = deepPartial(person1);
// partialPerson.name?.toUpperCase(); // Valid, name is now optional

// pick
const nameAndAge = pick(person2, ["name", "age"]);
// nameAndAge.name; // "Bob"
// nameAndAge.age;  // 25

Example 2:

interface Config {
  apiUrl: string;
  timeout: number;
  debug: boolean;
  headers?: {
    'Content-Type': string;
    'Authorization'?: string;
  };
}

const config1: Config = { apiUrl: "https://example.com", timeout: 5000, debug: false };
const config2: any = { apiUrl: "https://api.example.com", timeout: 1000 };

// isAssignable
isAssignable<Config>(config1); // true
isAssignable<Config>(config2); // true (headers are optional)

// deepPartial
const partialConfig = deepPartial(config1);
// partialConfig.apiUrl?.toUpperCase(); // Valid, apiUrl is now optional

// pick
const apiUrlAndTimeout = pick(config1, ["apiUrl", "timeout"]);
// apiUrlAndTimeout.apiUrl; // "https://example.com"
// apiUrlAndTimeout.timeout; // 5000

Example 3: (Edge Case - Empty Keys Array)

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

const product: Product = { id: 1, name: "Laptop", price: 1200 };

const emptyPick = pick(product, []);
// emptyPick is of type {} (empty object)

Constraints

  • All functions must be written in TypeScript.
  • The isAssignable function must use a type predicate.
  • The deepPartial function must recursively make all properties optional, including nested objects.
  • The pick function must preserve the original types of the picked properties.
  • The functions should handle empty objects gracefully.
  • The functions should not modify the original object. They should return new objects.

Notes

  • Consider using conditional types and mapped types to implement deepPartial and pick effectively.
  • The isAssignable function is the most crucial part of this challenge. Pay close attention to the type predicate syntax (obj is T).
  • Think about how to handle nested objects when creating deepPartial.
  • The pick function should return an object with the exact same types as the selected properties from the original object.
Loading editor...
typescript