Glory to Russia
Glory to Russia

Reputation: 18760

Why am I getting the "wrong type" error here?

I have following two TypeScript classes:

export class AbsenceWithUser {
    @jsonProperty(Absence) public readonly absence: Absence;
    @jsonProperty(User) public readonly user: User;

    constructor(absence: Absence, user: User) {
        this.absence = absence;
        this.user = user;
    }
}

export class Birthday {
    @jsonProperty(MomentDateJson) public readonly birthday: moment.Moment;
    @jsonProperty(User) public readonly user: User;

    constructor(birthday: moment.Moment, user: User) {
        this.birthday = birthday;
        this.user = user;
    }
}

I need to create an array, elements of which can be instances of either AbsenceWithUser or Birthday. I do this using the following method:

private getAllEvents():(AbsenceWithUser|Birthday)[] {
    const absences = this.props.absences as (AbsenceWithUser|Birthday)[];
    const birthdays = this.props.birthdays as (AbsenceWithUser|Birthday)[];
    return absences.concat(birthdays);
}

However, if I create an array using getAllEvents, its elements are not recognized as instances of either class. I have the following function:

private eventToDate(x:AbsenceWithUser | Birthday):Moment {
    if (x instanceof AbsenceWithUser) {
        return x.absence.endDate;
    } else if (x instanceof Birthday) {
        return x.birthday;
    } else {
        throw new Error("Unknown event type, x: " + x);
    }
}

The following code produces an Error "Unknown event type".

const events = this.getAllEvents();
this.eventToDate(events[0]);

How should I change my code so that I can distinguish between AbsenceWithUser and Birthday in eventToDate?

Note that it doesn't need to be instanceof.

Update 1 (13:27 01.10.2017 MSK):

Tried to change getEvents method to this:

private getAllEvents():(AbsenceWithUser|Birthday)[] {
    const absences:(AbsenceWithUser|Birthday) = this.props.absences as (AbsenceWithUser|Birthday)[];
    const birthdays:(AbsenceWithUser|Birthday) = this.props.birthdays as (AbsenceWithUser|Birthday)[];
    return absences.concat(birthdays);
}

Now I'm getting errors like Error:(127, 15) TS2322:Type '(AbsenceWithUser | Birthday)[]' is not assignable to type 'AbsenceWithUser | Birthday'. Type '(AbsenceWithUser | Birthday)[]' is not assignable to type 'Birthday'. Property 'birthday' is missing in type '(AbsenceWithUser | Birthday)[]'. in WebStorm.

Update 2 (13:31 01.10.2017 MSK): I tried to create an empty array and then use concat to merge absences and birthdays arrays into one. For this I create the following array:

const a:(AbsenceWithUser|Birthday) = [];

WebStorm reports this error: Error:(127, 15) TS2322:Type 'never[]' is not assignable to type 'AbsenceWithUser | Birthday'. Type 'never[]' is not assignable to type 'Birthday'. Property 'birthday' is missing in type 'never[]'.

I have no idea where never[] type comes from.

Update 3 (13:46 01.10.2017 MSK): Fixed obvious type bugs in getAllEvents.

private getAllEvents():(AbsenceWithUser|Birthday)[] {
    const absences:(AbsenceWithUser|Birthday)[] = this.props.absences as (AbsenceWithUser|Birthday)[];
    const birthdays:(AbsenceWithUser|Birthday)[] = this.props.birthdays as (AbsenceWithUser|Birthday)[];
    const allEvents:(AbsenceWithUser|Birthday)[] = absences.concat(birthdays);
    return allEvents;
}

Update 4 (14:01 01.10.2017 MSK):

Another attempt:

export type EventTypes = (AbsenceWithUser|Birthday);

private isEmpty(arr:EventTypes[]):boolean {
    return arr.length === 0;
}

private getAllEvents():EventTypes[] {
    const absences:EventTypes[] = this.props.absences as EventTypes[];
    const birthdays:EventTypes[] = this.props.birthdays as EventTypes[];
    const absencesEmpty:boolean = this.isEmpty(absences);
    const birthdaysEmpty:boolean = this.isEmpty(birthdays)
    if (absencesEmpty && !birthdaysEmpty) {
        return birthdays;
    } else if (absencesEmpty && birthdaysEmpty) {
        return [];
    } else if (!absencesEmpty && !birthdaysEmpty) {
        return absences.concat(birthdays);
    } else if (!absencesEmpty && birthdaysEmpty) {
        return absences;
    }
    return [];
}

Update 5 (15:28 01.10.2017 MSK): Final version:

private getAllEvents():EventTypes[] {
    const absences:EventTypes[] = this.props.absences.map(absenceJson => {
        return new AbsenceWithUser(absenceJson.absence, absenceJson.user);
    });
    const birthdays:EventTypes[] = this.props.birthdays.map(birthdayJson => {
        return new Birthday(birthdayJson.birthday, birthdayJson.user);
    });
    const absencesEmpty = this.isEmpty(absences);
    const birthdaysEmpty = this.isEmpty(birthdays);
    if (absencesEmpty && birthdaysEmpty) {
        return [];
    } else if (absencesEmpty && !birthdaysEmpty) {
        return birthdays;
    } else if (!absencesEmpty && birthdaysEmpty) {
        return absences;
    } else {
        return absences.concat(birthdays);
    }
}

Note that you can theoretically use Object.assign in order to convert objects deserialized from JSON to Typescript class instances (if your environment supports it).

Upvotes: 0

Views: 102

Answers (1)

Kokodoko
Kokodoko

Reputation: 28158

I have created an example that shows how to merge two arrays of a specific type into an array that can have items of both types. You can use instanceof to check the type.

class AbsenceWithUser {
    public readonly when: string;
    public readonly user: string;
}

class Birthday {
    public readonly day: string;
    public readonly user: string;
}

class Test {

    constructor(){
      let absences : AbsenceWithUser[] = [new AbsenceWithUser(), new AbsenceWithUser()]
      let birthdays : Birthday[] = [new Birthday(), new Birthday(), new Birthday()]

      let combined:(AbsenceWithUser | Birthday)[] = this.mergeArrays(absences, birthdays)

      if (combined[0] instanceof Birthday) {
        console.log("we have a birthday")
      } else { 
        console.log("we have an absence")
      }

    }

    private mergeArrays(absences: AbsenceWithUser[], birthdays: Birthday[]): (AbsenceWithUser | Birthday)[] {
        var allEvents : (AbsenceWithUser | Birthday)[] = [...birthdays].concat(...absences);
        return allEvents;
    }
}

Upvotes: 2

Related Questions