Skip to content

[Suggestion] Override and identifier! for Mapped Type and extend interface #18567

Description

@k8w

TypeScript Version: 2.4.0 / nightly (2.5.0-dev.201xxxxx)
2.5.2

Situation
There are lots of demand of more flexibility to Mapped Type. For example:

  1. Extend from a type and override some fields
  2. Turn optional field to required
  3. Only make some specific fields to optional

Suggestion

  1. Support multiple field definition in one type, the latter definition (same fields) will overwrite the previous
  2. Support identifier! for type definition which will remove null and undefined.

Code

type Full = {
  field1: string;
  field2: string;
  ....
  fieldN: string;
}

//Suggestion 1: Multiple fields in type definition and support override
type SomeOptional = {
  [P in keyof Full]: Full[P];  // extends
  [P in 'field1' | 'field2']? : Full[P];  // override field1 and field2 to optional
  xxx: string;  // and can add more fields.
}

//Suggestion 2: Identifer! to remove null and undefined
type SomeRequired = {
  [P in keyof SomeOptional]: SomeOptional[P];  // extends
  field1: SomeOptional[field1]!;  // ! will remove null and undefined from a type
}

Benifit

  1. It can resolve most problem with required and optional in current open issues. (like Add a new type Required #15012, Type to invert optionality of properties #16173, Mapped type optional -> required property issue #13224, Is this possible? Opposite of Partial Type #12578, )
  2. More flexible to extend and override, in limited complexity.

BTW, function getProperty in #12578 is a solution, but it cannot be used in declaration files.(.d.ts)

Activity

  1. mhegazy commented on Sep 19, 2017

    @mhegazy
    Contributor

    can you give us some use cases of each of these requests?

  2. k8w commented on Sep 20, 2017

    @k8w
    Author

    Mohamed Hegazy (@mhegazy)
    Case for CRUD db doc

    //A database doc type, for get
    interface DocForGet {
      id: number;  //must have id on exist doc, auto generated by db.
      updatedTime: string | null | undefined;  //only have updatedTime if it have been updated (except first create)
      field1: xxx;
      ...
      fieldN: xxx;
    }

    So for DocForGet:

    1. When you insert a new doc to db, id is optional, and can't have updatedTime.
    2. When you update a doc, updatedTime must have a value (can't be null or undefined).

    With this request, you can achieve this by:

    type DocForInsert = {
      [P in keyof DocForGet]: DocForGet[P];  // extend
      id?: DocForInsert[id];  // override id to optional
      updatedTime?: never;  // can't set updatedTime on first create
    }
    
    type DocForUpdate = {
      [P in keyof DocForGet]: DocForGet[P];  // extend
      updatedTime: DocForGet[updatedTime]!;  //override, identifier! would remove null and undefined
    }
  3. changed the title [-][Suggestion] Override and identifier! for Mapped Type[/-] [+][Suggestion] Override and identifier! for Mapped Type and extend interface[/+] on Sep 20, 2017
  4. mhegazy commented on Sep 21, 2017

    @mhegazy
    Contributor

    The first portion can be defined by using intersection types, (try it on typescript@next):

    type DocForInsert = DocForGet & {
        id?: DocForGet["id"];  // override id to optional
        updatedTime: never;  // force it to be never
    };

    the second part is tracked by #17948.

    Assuming that id and updatedTime have the same type across all data types, can you have something like:

    type Insert<T> = T & {
        id?: number;
        updatedTime: never;
    };
    
    type Get<T> = T & {
        id: number;
        updatedTime: string | null | undefined;
    };
    
    type Update<T> = T & {
        id: number;
        updatedTime: string;
    };
  5. k8w commented on Sep 22, 2017

    @k8w
    Author

    Mohamed Hegazy (@mhegazy)

    Partion 1, tried on typescript@2.6.0-dev.20170921, but get error:

    interface DocForGet {
        id: number;  //must have id on exist doc, auto generated by db.
        updatedTime: string | null | undefined;  //only have updatedTime if it have been updated (except first create)
        field1: string;
    }
    
    type DocForInsert = DocForGet & {
        id?: DocForGet["id"];  // override id to optional
        updatedTime: never;  // force it to be never
    };
    
    let test: DocForInsert = {
        field1: 'test',
    }

    Error:

    test.ts(12,5): error TS2322: Type '{ field1: string; }' is not assignable to type 'DocForInsert'.
    Type '{ field1: string; }' is not assignable to type 'DocForGet'.
    Property 'id' is missing in type '{ field1: string; }'.

  6. mhegazy commented on Sep 22, 2017

    @mhegazy
    Contributor

    You are correct, i was only thinking of the read part of it not the write. for that you need to use Overwrite.

    type Diff<T extends string, U extends string> = ({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T];
    type Omit<T, K extends keyof T> = { [P in Diff<keyof T, K>]: T[P] };
    type Overwrite<T, U> = { [P in Diff<keyof T, keyof U>]: T[P] } & U;
    
    interface DocForGet {
        id: number;  //must have id on exist doc, auto generated by db.
        updatedTime: string | null | undefined;  //only have updatedTime if it have been updated (except first create)
        field1: string;
    }
    
    type DocForInsert = Overwrite<DocForGet, { id?: DocForGet["id"]; updatedTime?: never }>;
    
    let test: DocForInsert = {
        field1: 'test',
    };
  7. typescript-bot commented on Dec 5, 2017

    @typescript-bot
    Contributor

    Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.

  8. locked and limited conversation to collaborators on Jun 14, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    QuestionAn issue which isn't directly actionable in code

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions