Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I cast an object with fields missing to another in Typescript?

Tags:

typescript

I am getting a problem with Typescript interfaces. I am trying to cast one object (with some fields missing such as createdBy) to another object but my casting is not working.

I hope someone can help.

Here are the interface files that I have:

interface IWord {
    ascii?: number;
    awl570?: boolean;
    awl570Sublist?: number;
    categoryId: number;
    frequency?: number;
    groupId: number;
    lessonId: number;
    name: string;
    nawl963?: boolean;
    nawl963D?: number;
    nawl963Sfi?: number;
    nawl963U?: number;
    statusId: number;
    syllables?: string;
    toeflMcG400?: boolean;
    toeic?: boolean;
    wordForms: IWordForm[];
    wordId: number;
    createdById: number;
    createdDate: string;
    modifiedById: number;
    modifiedDate: string;
}

interface IWordForm {
    definition: string;
    posId: number;
    sampleSentences: [ISampleSentence];
    sourceId: number;
    statusId: number;
    synonyms: [ISynonym];
    wordFormId: number;
    wordId: number;
    createdById: number;
    createdDate: string;
    modifiedById: number;
    modifiedDate: string;
}

I am trying to create this:

var wos.word = <IWord>{
    categoryId: 1,
    lessonId: 1,
    name: null,
    groupId: 1,
    statusId: Status.Clean,
    toefl: true,
    wordForms: <IWordForm[]>[],
    wordId: $stateParams.wordId
}

But getting the following error:

Severity Code Description Project File Line Suppression State Error TS2352 Neither type '{ categoryId: number; lessonId: number; name: null; groupId: number; statusId: Status; toefl: boo...' nor type 'IWord' is assignable to the other. Property 'createdById' is missing in type '{ categoryId: number; lessonId: number; name: null; groupId: number; statusId: Status; toefl: boo...'. admin C:\H\admin\admin\app\routes\words.ts 102 Active

like image 618
Samantha J T Star Avatar asked Jun 21 '16 03:06

Samantha J T Star


2 Answers

There is a working playground example

Well, because the assignment of the expression word = <IWord>{ ...} really does not contain createdByID, createdDate, modifedById, modifiedDate, we should simply make them nullable

interface IWord {
    ...
    createdById?: number;
    createdDate?: string;
    modifiedById?: number;
    modifiedDate?: string;
}

Other words - either we ask Typescript to check for us that these are NOT nullable... then we have to provide them. Or they could be missing, then the above solution is the way to go...

Test it in action here

like image 71
Radim Köhler Avatar answered Sep 27 '22 16:09

Radim Köhler


TL;DR

var word = <IWord><any>{ name: null };

Answer

As Radim Köhler noted, if these fields are not required for your interface to work, it will be semantically correct to mark them as optional.

interface IWord {
    categoryId: number;
    groupId: number;
    lessonId: number;
    // ...
    createdById?: number; // notice the added question marks
    createdDate?: string;
    modifiedById?: number;
    modifiedDate?: string;
}

Or pass undefined default values (like you already do with the name field):

var word = {
    categoryId: 1,
    lessonId: 1,
    name: null,
    // ...
    createdById: undefined,
    createdDate: undefined,
    // ...
}

However, there are situations where you need to create an object without some of the fields. E.g. you want to create a basic object and then pass it to some other function to add missing fields.

In this case you can either split interface:

interface IWordBase {
    // some of the fields that you use internally in your function
    categoryId: number;
    groupId: number;
    lessonId: number;
    frequency?: number;
    // ...
}
interface IWord extends IWordBase {
    // extra fields that are required in the external code, but not necessarily required when creating an object internally
    createdById: number;
    createdDate: string;
    modifiedById: number;
    modifiedDate: string;
}

Or use a hack:

// cast to <any> before casting to <IWord>
var word = <IWord><any>{
    categoryId: 1,
    lessonId: 1,
    name: null
}

Keep in mind that by using this hack you lose all the benefits of compile-time checks. If you genuinely forget a field some day, the compilation error will be suppressed and you'll get a runtime error instead.

like image 25
zlumer Avatar answered Sep 27 '22 16:09

zlumer