Safe Type Checking with Type Guards in TypeScript
TypeScript's static typing is a powerful tool, but sometimes you'll encounter situations where you receive data from external sources (like user input, APIs, or other functions) that don't perfectly match your expected types. Type guards allow you to narrow down the type of a variable within a specific scope, ensuring type safety and preventing runtime errors. This challenge will test your understanding of how to create and utilize type guards effectively.
Problem Description
You are tasked with building a system that processes data representing different types of vehicles. The data arrives as a generic Vehicle type, which can be either a Car or a Bicycle. Your goal is to write functions that can safely determine the type of a Vehicle and then access its specific properties without encountering TypeScript errors. You must implement type guards to achieve this.
Key Requirements:
- Define Interfaces: Create
CarandBicycleinterfaces with appropriate properties (e.g.,CarhasnumDoors,BicyclehashasBasket). - Define a Union Type: Create a
Vehicletype that is a union ofCarandBicycle. - Implement Type Guards: Write two type guard functions,
isCarandisBicycle, that take aVehicleas input and return a boolean indicating whether it's aCaror aBicycle, respectively. These functions should useinoperator ortypeofto check for specific properties. - Safe Property Access: Write a function
processVehiclethat takes aVehicleas input. Inside this function, use theisCarandisBicycletype guards to conditionally access properties of the vehicle, ensuring type safety.
Expected Behavior:
- The
isCarfunction should returntrueif the inputVehicleis aCarandfalseotherwise. - The
isBicyclefunction should returntrueif the inputVehicleis aBicycleandfalseotherwise. - The
processVehiclefunction should correctly access and log the appropriate properties based on the vehicle type, without TypeScript errors.
Edge Cases to Consider:
- Handle cases where the input
Vehiclemight not have any of the expected properties. While not strictly required to handle these cases (i.e., throw an error), your type guards should correctly identify the type even if properties are missing. - Consider how your type guards will behave with unexpected input types.
Examples
Example 1:
Input: { numDoors: 4 }
Output: "Processing a car with 4 doors."
Explanation: The input is a Car. The isCar type guard returns true, allowing access to numDoors.
Example 2:
Input: { hasBasket: true }
Output: "Processing a bicycle with a basket."
Explanation: The input is a Bicycle. The isBicycle type guard returns true, allowing access to hasBasket.
Example 3:
Input: { model: "Unknown" }
Output: "Unknown vehicle type."
Explanation: The input doesn't have numDoors or hasBasket. Both type guards return false, and the default case is executed.
Constraints
- The solution must be written in TypeScript.
- The code should be well-structured and readable.
- The type guards must be accurate and efficient.
- No external libraries are allowed.
Notes
- The
inoperator is a common and effective way to implement type guards in TypeScript. It checks if a property exists on an object. - The
typeofoperator can also be used, especially when dealing with primitive types or checking for specific constructor functions. - Remember that type guards only narrow the type within a specific scope (e.g., inside an
ifstatement). Outside that scope, the variable will still be of the union type. - Consider using discriminated unions for more complex scenarios. While not strictly required here, it's a good practice to be aware of.