TypeScript Strict Null Check Helpers
In modern TypeScript development, leveraging strict null checks (strictNullChecks: true in tsconfig.json) is a powerful way to catch common bugs related to null and undefined values at compile time. However, working with potentially null or undefined values can sometimes lead to verbose conditional checks. This challenge asks you to create utility functions that simplify and enforce strict null checks in a more elegant and readable way.
Problem Description
You are tasked with building a set of TypeScript utility functions that help manage values that could be null or undefined under strict null checks. These helpers should allow for cleaner code when dealing with optional properties, function arguments, or return values that might not be present.
The core requirements are:
-
requireValue<T>(value: T | null | undefined, errorMessage?: string): T: This function should take a value that could beT,null, orundefined.- If the
valueis notnullorundefined, it should return thevalueitself (typed asT). - If the
valueisnullorundefined, it should throw anError. - An optional
errorMessagestring can be provided. If provided and an error is thrown, this message should be used. If not provided, a default message like "Value is null or undefined" should be used.
- If the
-
unwrapOr<T>(value: T | null | undefined, defaultValue: T): T: This function should take a value that could beT,null, orundefined, and a default value of typeT.- If the
valueis notnullorundefined, it should return thevalueitself. - If the
valueisnullorundefined, it should return thedefaultValue.
- If the
-
unwrapOrElse<T>(value: T | null | undefined, getDefaultValue: () => T): T: This function should take a value that could beT,nullorundefined, and a function that returns aT.- If the
valueis notnullorundefined, it should return thevalueitself. - If the
valueisnullorundefined, it should call thegetDefaultValuefunction and return its result. This is useful when the default value is expensive to compute or depends on external factors.
- If the
Key Considerations:
- Type Safety: All helper functions must maintain strict TypeScript type safety. The return type should be correctly inferred and enforced.
strictNullChecksCompliance: Your solution must work seamlessly whenstrictNullChecksis enabled in thetsconfig.json. This means the input parameters must accurately reflect the possibility ofnullorundefined.- Clarity and Readability: The goal is to make code that handles optional values more concise and easier to understand.
Examples
Example 1: requireValue
function processUser(user: { name: string } | null | undefined) {
// Without helper:
// if (user === null || user === undefined) {
// throw new Error("User is missing!");
// }
// const userName: string = user.name; // TS error if no check
// With helper:
const verifiedUser = requireValue(user, "User object cannot be null or undefined.");
const userName: string = verifiedUser.name; // No TS error here
console.log(`Processing user: ${userName}`);
}
// Test cases:
processUser({ name: "Alice" }); // Output: Processing user: Alice
try {
processUser(null);
} catch (e: any) {
console.error(e.message); // Output: User object cannot be null or undefined.
}
try {
processUser(undefined);
} catch (e: any) {
console.error(e.message); // Output: User object cannot be null or undefined.
}
Example 2: unwrapOr
function getUserPreference(preference: string | null | undefined, defaultPref: string): string {
// Without helper:
// return preference === null || preference === undefined ? defaultPref : preference;
// With helper:
return unwrapOr(preference, defaultPref);
}
// Test cases:
console.log(getUserPreference("dark", "light")); // Output: dark
console.log(getUserPreference(null, "light")); // Output: light
console.log(getUserPreference(undefined, "system")); // Output: system
Example 3: unwrapOrElse
function getConfigurationValue(config: string | null | undefined, fallbackConfigProvider: () => string): string {
// Without helper:
// if (config !== null && config !== undefined) {
// return config;
// } else {
// return fallbackConfigProvider();
// }
// With helper:
return unwrapOrElse(config, fallbackConfigProvider);
}
// Test cases:
const expensiveFallback = () => {
console.log("Calculating expensive fallback...");
return "default_setting";
};
console.log(getConfigurationValue("user_setting", expensiveFallback)); // Output: user_setting (fallback not called)
console.log(getConfigurationValue(null, expensiveFallback)); // Output: Calculating expensive fallback...
// Output: default_setting
console.log(getConfigurationValue(undefined, () => "another_default")); // Output: another_default (fallback called)
Constraints
- The helper functions must be implemented in TypeScript.
- No external libraries should be used for these specific helper functions.
- The functions should be generic (
<T>) to work with any type. - The implementation should be efficient; no unnecessary computations or object creations.
Notes
- Consider how TypeScript's type narrowing works and how your functions leverage it.
- The
errorMessageinrequireValueshould be an optional parameter, meaning it can be omitted. - The
getDefaultValuefunction inunwrapOrElseshould be a function that returns the default value, not the value itself. This allows for lazy evaluation of the default.