简体   繁体   中英

Typescript: How to write a type guard to tell if a value is a member of a union?

I have an type defined like this:

export type Thing = "thinga" | "thingb"

Is there a way I can tell if an arbitrary string value that I have is part of that union?

let value1 = "thinga";
let value2 = "otherthing";

console.log("value1: " +  value1 <is in> Thing ); 
console.log("value2: " +  value2 <is in> Thing ); 

Where I want it to be true for value1, but false for value2. The actual use-case is that have an array of strings that I want to filter out if they're not in the definition (and possibly log) and then cast to the correct type.

The wrinkle is that the Thing type is generated code that gets compiled in with my code and the values can change - so I don't want to write conditional logic in a type guard hardcoded to the value. I don't control the thing that generates the code - otherwise I'm guessing in this case what I want to do would be easy if the type were a string enum instead.

I'm hoping that I can write some kind of typeguard that uses keyof or something similar? But I can't see how to make it work.

Typescript version is 2.8.1, though upgrading is not a huge issue.

I'm aware of this question: https://stackoverflow.com/a/50085718/924597 , but I don't think this is a duplicate because I can't use the answer on that question.

There is no way at runtime to access the values in the union of string literal types since all type information is erased at compile time. We can construct an array with the values in such a way that will cause a compile time error if the union type does change.

export type Thing = "thinga" | "thingb"
function getAllValues<T>()  {
    class Helper<TOriginal, TLeft extends TOriginal> {
        private data: TOriginal[] = [] ;
        private dummy!: TLeft;
        done<TThis extends Helper<any, never>>(this: TThis) : TOriginal[]{
            return this.data;
        }
        push<TValue extends TLeft>(value: TValue) : Helper<TOriginal, Exclude<TLeft, TValue>> {
            this.data.push(value);
            return this as any;
        }
    }

    return new Helper<T, T>();
}

let allValues = getAllValues<Thing>().push("thinga").push("thingb").done(); //Ok
let oldValues = getAllValues<Thing>().push("thinga").push("thingb").push("oldThing").done(); // error oldThinig is not part of the union
let missingValues = getAllValues<Thing>().push("thinga").done(); // error we are missing a value

Now that we have all the values, creating a type guard is simple:

const isThing = (function(){
    let allValues = getAllValues<Thing>().push("thinga").push("thingb").done();
    return function(s: string) : s is Thing {
        return allValues.indexOf(s as Thing) != -1; 
    }
})()

let value1 = "thinga";
let value2 = "otherthing";
console.log("value1: " + isThing(value1) ); 
console.log("value2: " + isThing(value2) ); 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM