TypeScript Nullish Coalescing Type Operator
This challenge focuses on replicating the behavior of TypeScript's nullish coalescing operator (??) at the type level. You will learn how to create types that elegantly handle potentially null or undefined values, providing a default value when necessary. This is a fundamental concept for writing robust and type-safe JavaScript/TypeScript applications.
Problem Description
Your task is to create a generic TypeScript type called NullishCoalesce<T, U> that mimics the runtime behavior of the nullish coalescing operator (??).
The nullish coalescing operator (??) returns its left-hand operand when it is not null or undefined. Otherwise, it returns its right-hand operand.
You need to define NullishCoalesce<T, U> such that:
- If
Tisnullorundefined,NullishCoalesce<T, U>should resolve toU. - Otherwise (if
Tis anything other thannullorundefined),NullishCoalesce<T, U>should resolve toT.
This type should be able to handle various primitive types, object types, union types, and even null and undefined directly.
Examples
Example 1:
type Example1 = NullishCoalesce<string | null, number>;
// Expected type: string | number
Explanation: string | null is not solely null or undefined, so the type resolves to the left-hand operand, string | null. However, since U is number, the resulting union includes number as well. The type effectively becomes string | number.
Example 2:
type Example2 = NullishCoalesce<undefined, boolean>;
// Expected type: boolean
Explanation: The left-hand operand undefined is one of the nullish values. Therefore, the type resolves to the right-hand operand, boolean.
Example 3:
type Example3 = NullishCoalesce<number, string>;
// Expected type: number
Explanation: The left-hand operand number is not null or undefined. Therefore, the type resolves to the left-hand operand, number.
Example 4:
type Example4 = NullishCoalesce<null | { name: string }, { age: number }>;
// Expected type: { name: string } | { age: number }
Explanation: The left-hand operand is a union type null | { name: string }. Since null is present, and we want to default when T is nullish, we need to consider the non-nullish part of T and U. The type should effectively be { name: string } | { age: number }.
Example 5:
type Example5 = NullishCoalesce<string, string | undefined>;
// Expected type: string | undefined
Explanation: The left-hand operand string is not nullish. Therefore, the type resolves to string. However, since the right-hand operand U is string | undefined, the overall resulting type needs to be assignable to both. So, the type becomes string | undefined.
Constraints
- The solution must be a single generic type alias named
NullishCoalesce<T, U>. - The solution must use only standard TypeScript type manipulation features (e.g., conditional types, union types).
- No runtime code is required; this is purely a type-level challenge.
Notes
Consider how conditional types in TypeScript can be used to check for null or undefined. You might need to use union types to combine the potential outcomes correctly. Think about how to elegantly handle the case where T itself is a union type that includes null or undefined. The goal is to return U only when T is specifically null or undefined, not when T is a union that might contain null or undefined but also has other non-nullish members.