TypeScript Type Assertion Helpers
In TypeScript, type assertions allow you to tell the compiler that you know more about a value's type than it can infer. While useful, they can sometimes lead to runtime errors if the assertion is incorrect. This challenge asks you to build robust helper functions that provide safer type assertions.
Problem Description
You are tasked with creating two type assertion helper functions in TypeScript: assertIs<T> and assertObjectIs<T>. These functions should provide a more controlled and safer way to perform type assertions compared to the standard as keyword or ! operator.
Key Requirements:
-
assertIs<T>(value: any, errorMessage?: string): asserts value is T:- This function takes any value and a type
Tto assert. - It should perform a runtime check to verify if the
valueis indeed assignable to typeT. - If the assertion fails, it should throw an
Errorwith the providederrorMessage(or a default message if none is given). - The return type
asserts value is Tis crucial. This tells the TypeScript compiler that after this function successfully executes without throwing an error, thevalueis guaranteed to be of typeT.
- This function takes any value and a type
-
assertObjectIs<T>(value: any, errorMessage?: string): asserts value is T:- This function is specifically designed for asserting that a value is an object that conforms to a specific interface or type
T. - It should first check if the
valueis a non-null object. - Then, it should iterate through the keys of the expected type
Tand check if each key exists as a property on thevalue. - If the
valueis not an object, or if any of the required properties are missing, it should throw anErrorwith the providederrorMessage(or a default message). - Similar to
assertIs, it must use theasserts value is Treturn type.
- This function is specifically designed for asserting that a value is an object that conforms to a specific interface or type
Expected Behavior:
- If
assertIsorassertObjectIssuccessfully validates the type, no error is thrown, and subsequent code within the same scope will treat the asserted value as the specified typeT. - If the validation fails, an
Erroris thrown, halting execution.
Edge Cases to Consider:
nullandundefinedvalues forassertIs.- Non-object values (primitives, arrays) for
assertObjectIs. - Empty objects for
assertObjectIs. - Objects with extra properties not defined in
TforassertObjectIs(these should be allowed, the check is for presence ofT's properties, not exclusivity). - The
errorMessageparameter being optional.
Examples
Example 1: assertIs with a primitive
function processNumber(input: any) {
assertIs<number>(input, "Input must be a number.");
// At this point, TypeScript knows 'input' is a number
console.log(input.toFixed(2));
}
processNumber(123.456); // Output: "123.46"
// processNumber("hello"); // Throws: Error: Input must be a number.
// processNumber(null); // Throws: Error: Input must be a number.
Example 2: assertObjectIs with an interface
interface User {
id: number;
name: string;
}
function displayUser(data: any) {
assertObjectIs<User>(data, "Data must be a User object.");
// At this point, TypeScript knows 'data' is a User
console.log(`User ID: ${data.id}, Name: ${data.name}`);
}
const userData = { id: 1, name: "Alice", age: 30 };
displayUser(userData); // Output: "User ID: 1, Name: Alice"
const invalidData = { id: 2 };
// displayUser(invalidData); // Throws: Error: Data must be a User object. (missing 'name')
const notAnObject = 123;
// displayUser(notAnObject); // Throws: Error: Data must be a User object.
Example 3: assertIs with a complex type
type Point = { x: number; y: number };
function movePoint(p: any): Point {
assertIs<Point>(p, "p must be a Point object.");
// TypeScript knows p is a Point here
p.x += 1;
p.y += 1;
return p;
}
const myPoint = { x: 5, y: 10 };
const moved = movePoint(myPoint); // moved is of type Point
console.log(moved); // Output: { x: 6, y: 11 }
const invalidPoint = { x: 1 };
// movePoint(invalidPoint); // Throws: Error: p must be a Point object. (missing 'y')
Constraints
- Your solution must be written entirely in TypeScript.
- The helper functions should be generic (
<T>). - The functions must use the
asserts value is Treturn type signature. - For
assertObjectIs, you only need to check for the presence of keys defined inT. You do not need to perform deep checks on nested object properties or value types within the properties themselves, beyond whatvalue is Timplies after the initial key checks.
Notes
- Remember to import or define any necessary types if you are defining interfaces or types for testing your helpers.
- Think about how you can check if a value is a "plain" object (i.e., not
nulland of typeobject, but not an array or other built-in object types). - The
assertskeyword is a type guard. Your functions should act as type guards to inform the TypeScript compiler about the narrowed type.