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.
-
isAssignable<T>(obj: any): obj is T: This function takes an objectobjof typeanyand checks if it is assignable to a typeT. It should return a boolean indicating whether the object conforms to the structure of typeT. Crucially, it must use a type predicate, returningobj is T. -
deepPartial<T>(obj: T): DeepPartial<T>: This function takes an objectobjof typeTand returns a new object where all properties ofTare optional. This is useful for creating partial updates or allowing for incomplete data. The return typeDeepPartial<T>is a utility type that recursively makes all properties optional, including nested objects. -
pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K>: This function takes an objectobjof typeTand an array of keyskeys(where each key is a property ofT). It returns a new object containing only the properties specified in thekeysarray, preserving their original types. The return typePick<T, K>is a utility type that creates a new type by picking a set of propertiesKfromT.
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
isAssignablefunction must use a type predicate. - The
deepPartialfunction must recursively make all properties optional, including nested objects. - The
pickfunction 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
deepPartialandpickeffectively. - The
isAssignablefunction 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
pickfunction should return an object with the exact same types as the selected properties from the original object.