169

In TypeScript, 2.2...

Let's say I have a Person type:

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

And I'd like to create a function that returns a Person, but doesn't require a nickname:

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

What should be the type of input? I'm looking for a dynamic way to specify a type that is identical to Person except that nickname is optional (nickname?: string | undefined). The closest thing I've figured out so far is this:

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

but that's not quite what I'm looking for, since I have to specify all the types that are required instead of the ones that are optional.

DallonF
  • 2,816
  • 3
  • 22
  • 23
  • be aware, most of the answers (if not all till this date), loses information on the union type, as in this `type Description = { id: string } & ({ type: "hooman"; iq: number } | { type: "meow"; fur: "average" | "less" }); let a: Omit = { type: "hooman", iq: -200, };` – Valen Aug 09 '22 at 16:53

10 Answers10

256

You can also do something like this, partial only some of the keys.

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>

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

type MakePersonInput = PartialBy<Person, 'nickname'>
Damian Pieczyński
  • 2,706
  • 2
  • 16
  • 6
  • 12
    This is awesome! Note you can pass more than one property: type OnlyNameIsMandatory = PartialBy – epere4 Apr 25 '19 at 12:02
  • 58
    A quick note, the `Omit` helper type is now available by default in [TypeScript 3.5+](https://devblogs.microsoft.com/typescript/announcing-typescript-3-5/#the-omit-helper-type). So if you are using TS 3.5 or above you no longer need to define `Omit` yourself. – Sly_cardinal Jul 24 '19 at 02:57
  • what if I also want to add some extra properties to MakePersonInput? – Thanos M Apr 06 '20 at 14:39
  • 2
    @ThanosM you can use `interface PersonExtended extends MakePersonInput { some_prop: string; }` or intersection type `type MakePersonInput = PartialBy & {some_prop: string}` – Brachacz Jan 28 '21 at 12:47
  • 9
    `Pick` actually doesn't seem to be necessary: `Omit & Partial` gets the job done as well. – derpedy-doo Mar 24 '21 at 22:54
  • Hi, how about nested atrribute something like If there an attribute object that need to be partial , how can i do it ? For exemple, I want address to be partial ` interface Person { name: string; hometown: string; nickname: string; address: Address } interface Address { street: string, city: string } ` – inttyl Apr 01 '21 at 17:59
171

Here is my Typescript 3.5+ Optional utility type

type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;

// and your use case
type MakePersonInput = Optional<Person, 'nickname'>

// and if you wanted to make the hometown optional as well
type MakePersonInput = Optional<Person, 'hometown' | 'nickname'>
Tim Krins
  • 3,134
  • 1
  • 23
  • 25
  • 8
    This is beautyful! – Sebastiandg7 Feb 23 '22 at 03:38
  • 1
    Awesome! Would you mind to explain the logic behind it? – André Vendramini Jun 09 '22 at 16:53
  • 2
    Sure! `Optional` takes two arguments, `T` is the type we want to base our optional type, and `K` represents a set of keys that are available on the type `T`. `Partial` returns a type with all of the keys in `T` marked as optional. Surrounding the `Partial` with a `Pick<...,K>` gives us a type with only the keys that we supplied, which we have already made optional. Using `Omit` gives us a type without any of the keys that we have specified. By using an `&`, it will union the two types together. – Tim Krins Jun 10 '22 at 09:42
  • Mulling it over more, there might be a shorter way of accomplishing the same thing - but feels cleaner in my mind building two types with no overlapping keys and merging them together. – Tim Krins Jun 10 '22 at 09:45
  • in that case there is: `type Optional = Pick> & Pick, K>` – alfred Feb 10 '23 at 04:25
  • `Pick>` is the underlying source of `Omit` in `lib.es5.d.ts`. – Tim Krins Feb 10 '23 at 15:46
  • I wanted the opposite of this...make all properties optional except for the ones I specify. This is what I came up with: `type MostlyOptional = Partial & Pick;` I'd share a link to the TS Playground demo'ing this but the URLs are too dang long! – Dustin Wyatt May 05 '23 at 20:55
39

For a plug and play solution, consider using the brilliant utility-types package:

npm i utility-types --save

Then simply make use of Optional<T, K>:

import { Optional } from 'utility-types';

type Person = {
  name: string;
  hometown: string;
  nickname: string;
}

type PersonWithOptionalNickname = Optional<Person, 'nickname'>;

// Expect:
//
// type PersonWithOptionalNickname {
//   name: string;
//   hometown: string;
//   nickname?: string;
// }
Philip Bulley
  • 9,014
  • 3
  • 33
  • 46
21

Update:

As of TypeScript 2.8, this is supported much more concisely by Conditional Types! So far, this also seems to be more reliable 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 is equivalent to:

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

Outdated:

As of TypeScript 2.4.1, it looks like there's another option available, as proposed by GitHub user ahejlsberg in a thread on type subtraction: 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 is equivalent to:

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

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

On the downside, I'm gonna need to stare at that Diff type for a while before I start to understand how it works.

DallonF
  • 2,816
  • 3
  • 22
  • 23
10

The type-fest package has a utility SetOptional - https://github.com/sindresorhus/type-fest/blob/main/source/set-optional.d.ts

import { SetOptional } from 'type-fest';

type PersonWithNicknameOptional = SetOptional<Person, 'nickname'>

I find the library is well-maintained, and supports latest versions of typescript. It's worth adding in a typescript project IMO.

Peter Krieg
  • 352
  • 1
  • 4
  • 10
6

If you use a recent version of typescript, a simple solution is to do

function makePerson(input: Omit<Person, 'nickname'> & { nickname?: string }): Person {
  return {...input, nickname: input.nickname || input.name};
}

Basically you remove the "nickname" property from the interface and re-add it as optional

If you want to make sure to keep it in sync with the original interface you can do

Omit<Person, 'nickname'> & Partial<Pick<Person, 'nickname'>>

which will warn you if you ever change the "nickname" prop in the original interface

Alex Beauchemin
  • 1,072
  • 1
  • 17
  • 24
0

Ok well what you are really describing is two different "Types" of people (i.e. Person types) .. A normal person and a nick named person.

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

interface NicknamedPerson extends Person {
    nickname: string;
}

Then in the case where 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 wanted to hang on to just one Person interface is having a different implementation for a non nicknamed person:

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 enables you to still assign a nickname (which is just the persons name in case of an absence of a nickname) and still uphold the Person interface's contract. It really has more to do with how you intend on using the interface. Does the code care about the person having a nickname? If not, then the first proposal is probably better.

Brian Gorman
  • 794
  • 4
  • 8
  • Hi... so... this is a great response to the question I asked, but not so much for what I'm actually trying to solve :/ Sorry for the oversimplification of the question. In the terms of what I asked... basically, every Person _must_ have a nickname, but I don't want this function to require it. If it's not provided, it'll just use the `name` input. – DallonF Apr 01 '17 at 20:42
  • I edited my answer to better illustrate the second option I was advocating. Let me know if we still aren't on the same page? Notice that the RegularPerson constructor makes the "nickname" value equal to the "name" value of input. – Brian Gorman Apr 01 '17 at 21:45
  • `input` here is still missing a type, which is the tricky part of this question... I think what I'm trying to do just isn't possible in TypeScript yet. I think I'm going to self-answer with what I found. – DallonF Apr 02 '17 at 13:49
0

After a lot of digging, I think what I'm trying to do just isn't possible in TypeScript... yet. When spread/rest types land, I think it will be, though, with syntax something along the lines of { ...Person, nickname?: string }.

For now, I've gone with a more verbose approach, declaring the properties that are required:

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 more required properties to Person, but it's impossible to forget to do this, because it will cause a type error in makePerson.

DallonF
  • 2,816
  • 3
  • 22
  • 23
0
export type Optional<T, K extends keyof T> = Omit<T, K> & { [P in keyof T]?: T[P] | undefined; }

If you want to cover the case when the key is not passed at all, or it is passed but with value set to undefined

Lachezar
  • 6,523
  • 3
  • 33
  • 34
-1

I reformatted your code to this:

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

TypeScript 5. The question mark "?" denotes an optional property or method in an interface or class.

In the code I provided, the "nickname" property is marked with a question mark, which means that it's optional. It indicates that instances of the Person interface may or may not have a nickname property.

If a Person object has a nickname property, it must be a string type. However, if it doesn't have a nickname property, it won't cause a compilation error. This makes it possible to define objects that conform to the Person interface with or without a nickname property.

  • that does not help to answer the question asked. Noone wants to rewrite manually the type definition with optional property, the question was about how to transfer a type to another type in a functional manner. – grenobnik Jul 07 '23 at 12:22