skip to Main Content

I have two classes, A and B, a predicate function for each of these classes (isA and isB), and an array arr which holds a couple of instances of these two classes. When filtering the array using the predicate function it correctly returns just the requested instances and on the type level reduces to an array of just the requested class:

class A { }
class B { }

const isA = (
    x: any
): x is A => {
    return x instanceof A;
};

const isB = (
    x: any
): x is B => {
    return x instanceof B;
};

const arr: Array<A | B> = [new A(), new A(), new B()]

const onlyA = arr.filter(isA) // correct type: const onlyA: A[]
const onlyB = arr.filter(isB) // correct type: const onlyB: B[]

My goal is to write a function getInstance() that takes an array with string values "a" and/or "b" and returns only the instances of this/these name(s), e.g. getInstance(["a"]) should have the same return value as onlyA above, and getInstance(["a", "b"]) should return instances of A and B and on the type level (A | B)[].

I tried to create a new object Obj that relates the strings to the predicate functions. I make getInstance() generic and constrain its only argument to the keys of Obj. However, this implementation doesn’t work on the type level.

const Obj = {
    a: isA,
    b: isB
}


const getInstance = <T extends keyof typeof Obj>(types: T[]) => {
    const fns = types.map(type => Obj[type])
    const set = fns.map(fn => arr.filter(() => fn))

    return set.flat(1) 
}

const retA = getInstance(["a"]) // wrong return type: (A | B)[]; should return A[]
const retAB = getInstance(["a", "b"]) // should return (A | B)[]

2

Answers


  1. const ObjMap = {
        a: A,
        b: B
    }
    
    const getInstance = <T extends keyof typeof Obj>(types: T[]) => {
        const fns = types.map(type => Obj[type])
        const set = fns.map(fn => types.filter(fn))
        return set.flat(1).map(type => ObjMap[type])
    }
    
    const retA = getInstance(["a"]) // (typeof A)[]
    const retAB = getInstance(["a", "b"]) // (typeof A | typeof B)[]
    
    
    Login or Signup to reply.
  2. There is new feature in TS5 called const Type Parameters

    Note a few amendments:

    • the generic param is now const
    • I take keys as varargs, not as an array
    • your filtering logic was incorrect
    type TypeMap = {
      a: A
      b: B
    }
    
    const typePredicates = {
        a: isA,
        b: isB,
    }
    
    const getInstance = <const T extends keyof TypeMap>(...types: T[]): (TypeMap[T])[] => {
        const fns = types.map(type => typePredicates[type])
        const filtered = fns.flatMap(fn => arr.filter(fn))
        return filtered;  
    }
    
    const retA = getInstance("a");      // A []
    console.log('RetA: ', retA);
    
    const retAB = getInstance("a", "b") // (A | B)[]
    console.log('RetAB: ', retAB);
    

    Playground link

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search