0

I have a base class, and sub-classes there of.

Instances of those classes get put in an collection with the type of the base class.

class Type extends Object {
    public static ID = 'type';
    public id = 'type';
    constructor() { super(); }
}

class TypeA extends Type {
    public static ID = 'type-a';
    public id = 'type-a';
    constructor() { super(); }
    public onlyA() { return 'only A has this method'; }
}

class TypeB extends Type {
    public static ID = 'type-b';
    public id = 'type-b';
    constructor() { super(); }
    public onlyB() { return 'only B has this method'; }
}

// Discards subclass type information:
const list: Type[] = [
    new TypeA(),
    new TypeB()
];

// Has inferred type: Type
const list0 = list[0];

Now if I know the correct type, I can use as to promote the type:

const list0asA = list0 as TypeA;
list0asA.onlyA();

What I'd like to do however is create a generic function that will dynamically check the instance, and return either the promoted type or null if it does not match.

I came up with the following, but it's not quite right:

function castOrNull<
    C extends typeof Type
>(value: Type, Constructor: C): C | null {
    if (value.id !== Constructor.ID) {
        return null;
    }
    return value as C;
}

const list0castA = castOrNull(list0, TypeA);
if (list0castA) {
    list0asA.onlyA();
}

The problem is I'm not trying to cast the variable to the constructors type, but the type of an instance of that constructor, so the as and return types are not correct.

Alternately, this does work, but it requires explicitly setting the generic type, meaning specifying the type twice when used, which is less-than-ideal.

function castOrNull<
    T extends Type
>(value: Type, Constructor: typeof Type): T | null {
    if (value.id !== Constructor.ID) {
        return null;
    }
    return value as T;
}

const list0upA = castOrNull<TypeA>(list0, TypeA);
if (list0castA) {
    list0asA.onlyA();
}

Is it possible to create this generic function without specifying the type twice?

2
  • Is typescript 2.8 an option? If so, you can use InstanceType<C> for your return type and cast. Commented Apr 11, 2018 at 20:37
  • @CRice Yep! That looks good! Care to post an answer? Commented Apr 11, 2018 at 20:38

1 Answer 1

1

Starting in Typescript 2.8, the type InstanceType<T> was added to the standard lib, which extracts from a constructor type T the type of its instance. So for your snippet, you can use that for your return type and cast:

function castOrNull<
    C extends typeof Type
>(value: Type, Constructor: C): InstanceType<C> | null {
    if (value.id !== Constructor.ID) {
        return null;
    }
    return value as InstanceType<C>;
}

// All good now
const list0castA = castOrNull(list0, TypeA);
if (list0castA) {
    list0asA.onlyA();
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.