Hone logo
Hone
Problems

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:

  1. assertIs<T>(value: any, errorMessage?: string): asserts value is T:

    • This function takes any value and a type T to assert.
    • It should perform a runtime check to verify if the value is indeed assignable to type T.
    • If the assertion fails, it should throw an Error with the provided errorMessage (or a default message if none is given).
    • The return type asserts value is T is crucial. This tells the TypeScript compiler that after this function successfully executes without throwing an error, the value is guaranteed to be of type T.
  2. 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 value is a non-null object.
    • Then, it should iterate through the keys of the expected type T and check if each key exists as a property on the value.
    • If the value is not an object, or if any of the required properties are missing, it should throw an Error with the provided errorMessage (or a default message).
    • Similar to assertIs, it must use the asserts value is T return type.

Expected Behavior:

  • If assertIs or assertObjectIs successfully validates the type, no error is thrown, and subsequent code within the same scope will treat the asserted value as the specified type T.
  • If the validation fails, an Error is thrown, halting execution.

Edge Cases to Consider:

  • null and undefined values for assertIs.
  • Non-object values (primitives, arrays) for assertObjectIs.
  • Empty objects for assertObjectIs.
  • Objects with extra properties not defined in T for assertObjectIs (these should be allowed, the check is for presence of T's properties, not exclusivity).
  • The errorMessage parameter 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 T return type signature.
  • For assertObjectIs, you only need to check for the presence of keys defined in T. You do not need to perform deep checks on nested object properties or value types within the properties themselves, beyond what value is T implies 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 null and of type object, but not an array or other built-in object types).
  • The asserts keyword is a type guard. Your functions should act as type guards to inform the TypeScript compiler about the narrowed type.
Loading editor...
typescript