0

Let I have two interfaces that have a few fields in common and another interfaces that generalizes them:

interface IFirst {
    common: "A" | "B";
    private_0: string;
}
interface ISecond {
    common: "C" | "D";
    private_1: string;
}
interface ICommon {
    common: string;
    private_0?: string;
    private_1?: string;
}

Now, I want to write a function that prints an instance of these interfaces. I decided to use overloading:

function printElement(element: IFirst) : void;
function printElement(element: ISecond): void;

function printElement(element: ICommon) : void {
    console.log(element.common);
    if (element.private_0)
        console.log(element.private_0);
    if (element.private_1)
        console.log(element.private_1);
}

Then I want to write a function that prints an array of them:

function printAll<ElementType extends ICommon>(array: ElementType[]) {
    for (const element of array)
        printElement(element)
}

However, this doesn't work:

No overload matches this call.   Overload 1 of 2, '(element: IFirst): void', gave the following error.
    Argument of type 'ElementType' is not assignable to parameter of type 'IFirst'.
      Type 'ICommon' is not assignable to type 'IFirst'.
        Types of property 'common' are incompatible.
          Type 'string' is not assignable to type '"A" | "B"'.   Overload 2 of 2, '(element: ISecond): void', gave the following error.
    Argument of type 'ElementType' is not assignable to parameter of type 'ISecond'.
      Type 'ICommon' is not assignable to type 'ISecond'.
        Types of property 'common' are incompatible.
          Type 'string' is not assignable to type '"C" | "D"'.(2769)

Because ElementType is considered as ICommon instance. The compiler tries to do a backwards conversion from ICommon to IFirst, for example, and it is obviously illegal. How do I make this function type safe then?

ivaigult
  • 6,198
  • 5
  • 38
  • 66
  • 1
    Why not have `type ICommon = IFirst | ISecond` or maybe `interface IFirst extends ICommon`? Right now these interfaces aren't actually related, the `ICommon` just sort of looks like either but isn't. Having `{common: "A", private_1: "hello"}` is a valid assignment for `ICommon` but matches neither of the other interfaces. – VLAZ Apr 29 '20 at 08:57

1 Answers1

1

You can achieve the kind of thing you're looking for with something like this:

interface IFirst {
    common: "A" | "B";
    private_0: string;
}

interface ISecond {
    common: "C" | "D";
    private_1: string;
}

type ICommon = IFirst | ISecond;

function printElement(element: ICommon) : void {
    console.log(element.common);
    if ("private_0" in element) {
        console.log(element.private_0);
    }
    if ("private_1" in element) {
        console.log(element.private_1);
    }
}

function printAll(array: ICommon[]) {
    for (const element of array) {
        printElement(element);
    }
}

And you can improve the type checking with proper functions as described in Interface type check with Typescript

DomAyre
  • 828
  • 1
  • 11
  • 23