Home > database >  Typescript types that can be overwritten based on type arguments
Typescript types that can be overwritten based on type arguments

Time:05-22

I'm trying to figure out the best way to tell the typescript compiler that a function that returns T | null will definitely be T in certain circumstances.

Perhaps I'm thinking about this wrong in which case I'm also open to new ideas, here is the snippet:

type ValueOrNull<T, N> = N extends false ? T | null : T;

export function getCookie<T = any, N = false>(name: string): ValueOrNull<T, N> {
  const match = document.cookie.match(new RegExp('(^| )'   name   '=([^;] )'));

  if (match) {
    const [, , value] = match;

    return parseCookieValue(value) as T;
  }

  return null;
}

My thinking was if I could call the function as follows: getCookie<TMyCookie, true>("my_cookie") that typescript would know that I'm sure the cookie would be there, and the function would not return null. For example after a successful login.

N extends false ? feels wrong but N === false doesn't work.

the compiler error is Type 'null' is not assignable to type 'ValueOrNull<T, N>'.ts(2322)

Thanks a lot

CodePudding user response:

You can do that fairly easily with function overloads and a second parameter; that also lets you build in a useful explanatory error when (inevitably) the programmer "knows" the cookie exists but it dooesn't:

export function getCookie<T>(name: string, required: true): T;
export function getCookie<T>(name: string, required?: false): T | null;
export function getCookie<T>(name: string, required?: boolean): T | null {
    const match = document.cookie.match(new RegExp('(^| )'   name   '=([^;] )'));

    if (match) {
        const [, , value] = match;
        // You'll want to put your `parseCookingvalue` back here,
        // the `as any as T` is just because we don't have that
        // function available to use in the question.
        return /*parseCookieValue(value)*/ value as any as T;
    } else if (required) {
        throw new Error(`Required cookie "${name}" was not found`);
    }
    return null;
}

const a = getCookie<string>("a", true);
//    ^? −−−− type is string
const b = getCookie<string>("b", false);
//    ^? −−−− type is string | null

Playground link


Here's an alternative for you, though: You could leave getCookie fairly simple and have a general-use type assertion function that you can use anywhere you get back something that may be null or undefined and you "know" it's not null or undefined:

export function getCookie<T>(name: string): T | null {
    const match = document.cookie.match(new RegExp('(^| )'   name   '=([^;] )'));

    if (match) {
        const [, , value] = match;
        // You'll want to put your `parseCookingvalue` back here,
        // the `as any as T` is just because we don't have that
        // function available to use in the question.
        return /*parseCookieValue(value)*/ value as any as T;
    }
    return null;
}

export function assertIsNotNullish<T>(value: T | null | undefined): asserts value is T {
    if (value == null) {
        throw new Error(`Expected non-nullish value, got ${value}`);
    }
}

const a = getCookie<string>("a");
assertIsNotNullish(a);
console.log(a);
//          ^? −−−− type is string
const b = getCookie<string>("b");
console.log(b);
//          ^? −−−− type is string | null

Playground link

  • Related