Hone logo
Hone
Problems

TypeScript: Mastering the Unknown with Advanced Type Utilities

In TypeScript, unknown is a powerful type that represents any value. However, it comes with the caveat that you must perform type checking before you can operate on a variable of type unknown. This challenge focuses on creating robust type utility functions to safely narrow down and work with unknown types, enhancing code safety and developer experience.

Problem Description

Your task is to implement a set of TypeScript utility functions that help safely narrow down and work with values of type unknown. These helpers should provide a more ergonomic and type-safe way to handle potentially unknown data, such as data fetched from external APIs or user input.

You will need to implement the following functions:

  1. isString(value: unknown): value is string

    • Checks if the given value is a string.
    • Should return true if it's a string, false otherwise.
  2. isNumber(value: unknown): value is number

    • Checks if the given value is a number.
    • Should return true if it's a number, false otherwise.
  3. isBoolean(value: unknown): value is boolean

    • Checks if the given value is a boolean.
    • Should return true if it's a boolean, false otherwise.
  4. isObject(value: unknown): value is object

    • Checks if the given value is a plain JavaScript object (i.e., not null, and of type 'object').
    • Should return true if it's a plain object, false otherwise.
  5. isArray(value: unknown): value is any[]

    • Checks if the given value is an array.
    • Should return true if it's an array, false otherwise.
  6. is dalam (value: unknown, validator: (v: unknown) => boolean): value is T (where T is inferred from the validator)

    • A generic function that takes a value of unknown and a validator function.
    • The validator function should accept unknown and return a type predicate (e.g., v is MySpecificType).
    • This function should return true if the validator returns true for the value, and false otherwise. It should also correctly infer and use the specific type T that the validator narrows down to.

Examples

Example 1: Basic Type Checks

let data: unknown = "hello";

if (isString(data)) {
  console.log(data.toUpperCase()); // Output: HELLO
}

data = 123;
if (isNumber(data)) {
  console.log(data.toFixed(2)); // Output: 123.00
}

data = true;
if (isBoolean(data)) {
  console.log(!data); // Output: false
}

data = { name: "Alice" };
if (isObject(data)) {
  console.log(data.name); // Output: Alice (Requires careful handling in TS for properties)
}

data = [1, 2, 3];
if (isArray(data)) {
  console.log(data.length); // Output: 3
}

Example 2: Using is for Complex Types

interface User {
  id: number;
  name: string;
}

const isUser = (value: unknown): value is User => {
  return (
    isObject(value) &&
    value !== null && // Redundant with isObject but good for clarity
    'id' in value && typeof value.id === 'number' &&
    'name' in value && typeof value.name === 'string'
  );
};

let potentialUser: unknown = { id: 1, name: "Bob" };

if (is dalam (potentialUser, isUser)) {
  console.log(`User ID: ${potentialUser.id}, Name: ${potentialUser.name}`); // Output: User ID: 1, Name: Bob
}

potentialUser = { id: 2, username: "Charlie" }; // Missing 'name' property
if (is dalam (potentialUser, isUser)) {
  // This block will not execute
} else {
  console.log("Not a valid User object."); // Output: Not a valid User object.
}

Example 3: Edge Cases

// isObject: null is of type 'object' in JS, but we want plain objects
let nullValue: unknown = null;
console.log(isObject(nullValue)); // Output: false

// isArray: Non-array objects should not be considered arrays
let notAnArray: unknown = { length: 3 };
console.log(isArray(notAnArray)); // Output: false

// isNumber: NaN is a number, but might need specific handling elsewhere
let nanValue: unknown = NaN;
console.log(isNumber(nanValue)); // Output: true

// is dalam: Validator returning false
const isPositiveNumber = (v: unknown): v is number => isNumber(v) && (v as number) > 0;
let negValue: unknown = -5;
if (is dalam (negValue, isPositiveNumber)) {
    // This block will not execute
} else {
    console.log("Value is not a positive number."); // Output: Value is not a positive number.
}

Constraints

  • All implemented helper functions must use type predicates (value is Type) to effectively narrow down types within TypeScript.
  • The isObject function should specifically exclude null from being considered a valid "object" for this challenge's purpose.
  • The is function must correctly infer the generic type T based on the provided validator's type predicate.
  • Performance is not a primary concern, but solutions should be reasonably efficient. Avoid overly complex or computationally expensive checks where simpler ones suffice.

Notes

  • Remember that JavaScript's typeof null returns 'object'. Your isObject implementation will need to handle this.
  • For the is function, consider how you can leverage TypeScript's conditional types and generic inference to make it work seamlessly with any valid type predicate.
  • Think about how these helpers can be used in real-world scenarios, like parsing JSON data or validating user inputs.
  • The is dalam function is a placeholder for a more descriptive name you might choose, like isOf, satisfies, or check. Choose a clear and concise name.
Loading editor...
typescript