Make one property optional in TypeScript

In TypeScript, 2.2 ...

Let's say I have a Person type:

interface Person {
  name: string;
  hometown: string;
  nickname: string;
}

      

And I would like to create a function that returns Person, but does not require a nickname:

function makePerson(input: ???): Person {
  return {...input, nickname: input.nickname || input.name};
}

      

What should be the type input

? I am looking for a dynamic way to specify a type that is identical Person

, except that nickname

is optional ( nickname?: string | undefined

). The closest I've figured out so far is the following:

type MakePersonInput = Partial<Person> & {
  name: string;
  hometown: string;
}

      

but this is not exactly what I'm looking for as I have to specify all types that are required instead of those that are optional.

+3


source to share


3 answers


Update:

As of TypeScript 2.8, this is supported much more succinctly by conditional types! So far, this also seems to be more robust than previous implementations.

type Overwrite<T1, T2> = {
    [P in Exclude<keyof T1, keyof T2>]: T1[P]
} & T2;

interface Person {
  name: string;
  hometown: string;
  nickname: string;
}

type MakePersonInput = Overwrite<Person, {
  nickname?: string;
}>

function makePerson(input: MakePersonInput): Person {
  return {...input, nickname: input.nickname || input.name};
}

      

As before, MakePersonInput

equivalent to:

type MakePersonInput = {
    name: string;
    hometown: string;
} & {
    nickname?: string;
}

      

Deprecated:



As of TypeScript 2.4.1, it looks like there is another option suggested by GitHub user ahejlsberg on a subtraction thread: https://github.com/Microsoft/TypeScript/issues/12215#issuecomment-307871458

type Diff<T extends string, U extends string> = ({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T];
type Overwrite<T, U> = { [P in Diff<keyof T, keyof U>]: T[P] } & U;

interface Person {
  name: string;
  hometown: string;
  nickname: string;
}
type MakePersonInput = Overwrite<Person, {
  nickname?: string
}>
function makePerson(input: MakePersonInput): Person {
  return {...input, nickname: input.nickname || input.name};
}

      

According to Intellisense, MakePersonInput

equivalent to:

type MakePersonInput = {
    name: string;
    hometown: string;
} & {
    nickname?: string;
}

      

which looks a little funny but absolutely gets the job done.

On the other hand, I will need to look at this type Diff

for a while before I begin to understand how it works.

+2


source


Well, what you are really describing are two different "types" of people (ie types of people). An ordinary person and a nickname named by a person.

interface Person {
    name: string;
    hometown: string;
}

interface NicknamedPerson extends Person {
    nickname: string;
}

      

Then in case you don't really want a nicknamed person but just a person, you just implement the Person interface.



An alternative way to do this, if you want to connect to one Person interface, has a different implementation for the person with no name:

interface Person {
  name: string;
  hometown: string;
  nickname: string;
}
class NicknamedPerson implements Person {
    constructor(public name: string, public hometown: string, public nickname: string) {}
}

class RegularPerson implements Person {
    nickname: string;
    constructor(public name: string, public hometown: string) {
        this.nickname = name;
    }
}

makePerson(input): Person {
     if(input.nickname != null) {
       return new NicknamedPerson(input.name, input.hometown, input.nickname);
     } else {
       return new RegularPerson(input.name, input.hometown);
     }
}

      

This allows you to still assign an alias (which is only the person's name in the absence of an alias) and still maintain a contract with the Person interface. It really has more to do with how you are going to use the interface. Does the code mean that the person has a nickname? If not, then the first sentence is probably better.

0


source


After a lot of digging, I think that what I am trying to do is not yet possible in TypeScript .... When spread / rest types , I think it will be, with syntax, something like strings { ...Person, nickname?: string }

.

I am currently taking a more verbose approach by declaring the required properties:

type MakePersonInput = Partial<Person> & {
  name: string;
  hometown: string;
};
function makePerson(input: MakePersonInput): Person {
  return {...input, nickname: input.nickname || input.name};
}

      

This unfortunately requires me to update MakePersonInput

whenever I add additional properties to Person

, but this is impossible to forget because it will throw a type error in makePerson

.

0


source







All Articles