Mastering Type Predicates in TypeScript
Type predicates are a powerful TypeScript feature that allow you to narrow down the type of a variable within a conditional block. This is incredibly useful for safely working with union types or when dealing with data that might not conform to a specific shape. This challenge will test your ability to define and utilize type predicates to create robust and type-safe code.
Problem Description
Your task is to implement type predicates in TypeScript to safely discriminate between different object types within a union. You will be provided with a union type representing various shapes of data, and you need to write functions that act as type guards for each specific type within that union.
What needs to be achieved:
- Define a union type
DataShapethat includes at least three distinct object types (e.g.,User,Product,Order). Each object type should have unique properties. - Implement individual type predicate functions for each of the object types defined in
DataShape. These functions should returntrueif a given input conforms to the specific type, andfalseotherwise. - Implement a main processing function that accepts an argument of type
DataShape. Inside this function, use your type predicate functions to determine the actual type of the input and perform specific actions based on that type.
Key requirements:
- All type predicate functions must use the
iskeyword in their return type signature (e.g.,value is SpecificType). - The main processing function should demonstrate type narrowing effectively, leveraging the type predicates to access specific properties of the narrowed types without type assertions.
Expected behavior:
The main processing function should print a descriptive string for each input it receives, indicating its type and perhaps one of its unique properties.
Important edge cases to consider:
- What happens if an input to a type predicate is
nullorundefined? Your predicates should handle this gracefully. - Consider inputs that might have some properties of a target type but not all. Your predicates should be strict.
Examples
Example 1:
Input (to main processing function):
let data1: DataShape = { type: "user", name: "Alice", userId: 123 };
let data2: DataShape = { type: "product", title: "Laptop", price: 1200 };
Output:
Processing User: Alice (ID: 123)
Processing Product: Laptop ($1200)
Explanation: The main function receives two different data shapes. The type predicates correctly identify each shape, allowing the function to access and print specific details for users and products.
Example 2:
Input (to main processing function):
let data3: DataShape = { type: "order", orderId: "ORD789", items: 5 };
let data4: DataShape = null;
Output:
Processing Order: ORD789 (Items: 5)
Cannot process null input.
Explanation: Demonstrates handling of another valid shape and an invalid (null) input. The type predicate for "order" works, and the main function handles the null case.
Example 3:
Input (to main processing function):
let data5: DataShape = { type: "user", name: "Bob" }; // Missing userId
let data6: DataShape = { type: "product", price: 50 }; // Missing title
Output:
Unknown data shape received.
Unknown data shape received.
Explanation: These inputs do not fully conform to any of the defined specific types. The type predicates should return false, and the main processing function should have a fallback for unrecognized shapes.
Constraints
- The
DataShapeunion type must include at least three distinct object types. - Each distinct object type must have at least two unique properties.
- Your solution should not use explicit type assertions (
as Type) within the main processing function when accessing properties of the narrowed types. - The solution should be written entirely in TypeScript.
Notes
- Think about how to differentiate your object types. Adding a
typeproperty with literal string values can be a common and effective strategy for discriminated unions. - Your type predicates will be the key to unlocking type safety. Pay close attention to their signature.
- Consider a default case in your main processing function to handle any data that doesn't match your defined types.