Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Typescript types that can be overwritten based on type arguments

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.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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

>Solution :

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

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading