Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript conditional is null

I'd like to create a function that takes an argument that is usually an object but might be null/undefined, and conditionally return the type if it's non-null, otherwise null.

function objOrNull<T>(t: T): T extends null ? null: T {
   if (t == null) return null;
   
   return t;
}

This function signature seems correct, I can call this with objOrNull({}), objOrNull({a:42}), objOrNull(null), objOrNull(undefined) and the returned variable has the expected type. But the function body fails to compile since neither return null nor return t is assignable to the declared return type. If I declare the return type as T | null, the function compiles, but whenever I call it, the type I get back is potentially null, which is obviously not what I want - it should be possible to determine in runtime that if t is null the returned value is also null, otherwise it's not?

Short of adding as any or similar ugly type casts, is there a "correct" way to implement such a function? For a value, what is the equivalent of the conditional type extends null ?? Why doesn't == null tell the compiler that I'm in the T extends null condition?

(My original use-case was a function that takes an object or null, and if the object is null returns null, otherwise returns a variant of the object with some properties added and some removed, but reducing the question to my core question.) My original usecase

like image 681
JHH Avatar asked Feb 07 '26 05:02

JHH


1 Answers

This is basically a current design limitation in TypeScript. The compiler does not use control flow analysis to narrow the type of unspecified generic type parameters (like T inside your function implementation), nor does it use it to narrow the type of values that depend on such type parameters (like t inside your function implementation).

This means that the compiler cannot verify that a value is assignable to a conditional type like T extends undefined ? null : T. Even if you check (typeof t === "undefined"), the control flow analysis that would normally narrow t from something concrete like string | undefined to undefined doesn't kick in for a type like T. Even if it did narrow t from T to (say) T & undefined, it doesn't narrow T itself to undefined, which is what would have to happen for the compiler to realize that null is assignable to T extends undefined ? null : T.

There's a canonical issue for this at microsoft/TypeScript#33912, asking for some way of having the compiler use control flow analysis to verify assignability of return values to generic conditional types. But I don't know if or when this will get addressed (although it's at least a good sign that it was proposed by one of the core members of the TS team).


This means that the best thing you can do for now is to use a type assertion (what you're calling an "ugly cast"):

function objOrNull<T>(t: T): T extends undefined ? null : T {
    return (typeof t === "undefined") ? null : t as any;
}

or the moral equivalent like a single call-signature overload:

function objOrNull<T>(t: T): T extends undefined ? null : T;
function objOrNull<T>(t: T): T | null {
    return (typeof t === "undefined") ? null : t;
}

That overload doesn't require you to use a type assertion, but it is unsafe in a similar way that using a type assertion. Meaning: either way should work to calm the compiler down, although it is not actually verifying safety here. That's your job since the compiler can't do it:

function badObjOrNull<T>(t: T): T extends undefined ? null : T {
    return (typeof t !== "undefined") ? null : t as any; // oops
}

or

function badObjOrNull<T>(t: T): T extends undefined ? null : T;
function badObjOrNull<T>(t: T): T | null {
    return (typeof t !== "undefined") ? null : t; // oops
}

Assuming you implement it properly, however, it should still behave as you expect from the caller's side:

const x = objOrNull(undefined); // null
console.log(x); // null

const y = objOrNull(Math.random() < 0.5 ? undefined : "hello"); // "hello" | null
console.log(y); // sometimes null, sometimes "hello"

Playground link to code

like image 144
jcalz Avatar answered Feb 08 '26 20:02

jcalz



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!