Nullish Coalescing Types in TypeScript
TypeScript's nullish coalescing operator (??) provides a concise way to handle null or undefined values. This challenge focuses on extending this concept to types, allowing you to define types that default to a specific type if the input type is null or undefined. This is particularly useful for optional properties or parameters where you want to ensure a default type is always present.
Problem Description
You are tasked with creating a utility type called NullishCoalesce. This type should take two type arguments, T and U, and return a new type that behaves as follows:
- If
Tisnullorundefined, the resulting type should beU. - Otherwise, the resulting type should be
T.
Essentially, NullishCoalesce<T, U> should provide a type-safe way to default to type U when T is nullish. This is analogous to the runtime behavior of the ?? operator, but applied at the type level.
Key Requirements:
- The utility type must correctly handle both
nullandundefinedas nullish values. - The utility type must preserve the original type
Twhen it is not nullish. - The utility type should be generic, accepting any valid TypeScript types for
TandU.
Expected Behavior:
NullishCoalesce<null, string>should resolve tostring.NullishCoalesce<undefined, number>should resolve tonumber.NullishCoalesce<string, number>should resolve tostring.NullishCoalesce<number, string>should resolve tonumber.NullishCoalesce<string | null, number>should resolve tostring | number.NullishCoalesce<{ a: string }, { b: number }>should resolve to{ a: string }.
Edge Cases to Consider:
- Types that already include
nullorundefined(e.g.,string | null). - Complex types like unions, intersections, and conditional types.
- Literal types.
Examples
Example 1:
type Result1 = NullishCoalesce<null, string>;
// Result1: string
Explanation: T is null, so the type resolves to U, which is string.
Example 2:
type Result2 = NullishCoalesce<string, number>;
// Result2: string
Explanation: T is string, which is not nullish, so the type resolves to T, which is string.
Example 3:
type Result3 = NullishCoalesce<string | null, number>;
// Result3: string | number
Explanation: T is string | null. Since it's a union including null, the type resolves to string | number.
Example 4:
type Result4 = NullishCoalesce<{ a: string }, { b: number }>;
// Result4: { a: string }
Explanation: T is an object with a property a: string. It's not nullish, so the type resolves to the original object type.
Constraints
- The solution must be written in TypeScript.
- The solution must be a valid TypeScript type definition.
- The solution should be as concise and readable as possible.
- The solution should handle all the expected behaviors and edge cases described above.
Notes
- You'll likely need to use conditional types to implement this utility type.
- Consider how to handle types that already include
nullorundefinedin their definition. You don't want to inadvertently change the type's meaning. - Think about how to ensure that the resulting type is as specific as possible while still providing the nullish coalescing behavior.