Search code examples
typescripttypescript2.8

Exclude type by member type


I have a function that handles situations where no data arrived, otherwise it calls a callback to handle actual data.

I use Typescript 2.8.3.

I've tried it this way, but it does not work:

interface Result { data: string | null; }

function doStuff(
    input: Result,
    cb: (str: Exclude<Result, {data: null}>) => void
): void {
    if (input.data) {
        cb({data: input.data});
    }
}

doStuff({data: "x"}, (result) => {
    alert(result.data.length); // TS2532: Object is possibly 'null'
});

I must preserve the Result interface because it is a generated code by some GQL library. This solution works, but is not possible for me:

interface VoidResult { data: null; }
interface NonVoidResult { data: string; }
type Result = VoidResult | NonVoidResult;
function doStuff(
    data: Result,
    cb: (str: NonVoidResult) => void
): void { ... }

The actual result object is not string but a complex object type that may even vary in future. Writing the callback like cb: (str: string) => void is therefore also not useful.

Is there a way to remove from type where an object member is null?


Solution

  • The type of Result is not a union, it's type taht has a member that is a union. You need to exclude null from the data property:

    interface Result { data: string | null; }
    
    function doStuff(
        input: Result,
        cb: (str: { data: Exclude<Result['data'], null> }) => void
    ): void {
        if (input.data) {
            cb({data: input.data});
        }
    }
    
    doStuff({data: "x"}, (result) => {
        alert(result.data.length); // ok
    });
    

    You can even create a type that excludes null from a specific type member. This can be useful if your interface has more members:

    interface Result { data: string | null; }
    type RemoveNullFrom<T, TKey extends keyof T> = Pick<T, Exclude<keyof T, TKey>> & {
        [P in TKey] : Exclude<T[P], null>    
    }
    function doStuff(
        input: Result,
        cb: (str: RemoveNullFrom<Result, 'data'>) => void
    ): void {
        if (input.data) {
            cb({data: input.data});
        }
    }
    
    doStuff({data: "x"}, (result) => {
        alert(result.data.length); // ok
    });