Make a single property optional in TypeScript

46,892

Solution 1

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'>

Solution 2

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'>

Solution 3

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;
// }

Solution 4

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.

Share:
46,892
DallonF
Author by

DallonF

Updated on October 22, 2021

Comments

  • DallonF
    DallonF over 2 years

    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
    DallonF about 7 years
    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.
  • Brian Gorman
    Brian Gorman about 7 years
    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.
  • DallonF
    DallonF about 7 years
    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.
  • joshhunt
    joshhunt almost 6 years
    Note that this makes any optional fields in the original interface required
  • andrey
    andrey over 5 years
    use this to keep optional fields - type Overwrite<T1, T2> = Pick<T1, Exclude<keyof T1, keyof T2>> & T2;
  • epere4
    epere4 about 5 years
    This is awesome! Note you can pass more than one property: type OnlyNameIsMandatory = PartialBy<Person, 'nickname'|'hometown'>
  • Sly_cardinal
    Sly_cardinal almost 5 years
    A quick note, the Omit helper type is now available by default in TypeScript 3.5+. So if you are using TS 3.5 or above you no longer need to define Omit yourself.
  • Thanos M
    Thanos M about 4 years
    what if I also want to add some extra properties to MakePersonInput?
  • Brachacz
    Brachacz about 3 years
    @ThanosM you can use interface PersonExtended extends MakePersonInput { some_prop: string; } or intersection type type MakePersonInput = PartialBy<Person, 'nickname'> & {some_prop: string}
  • electrovir
    electrovir about 3 years
    Pick<T,K> actually doesn't seem to be necessary: Omit<T, K> & Partial<T> gets the job done as well.
  • inttyl
    inttyl about 3 years
    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 } `
  • Sebastiandg7
    Sebastiandg7 about 2 years
    This is beautyful!
  • André Vendramini
    André Vendramini almost 2 years
    Awesome! Would you mind to explain the logic behind it?
  • Tim Krins
    Tim Krins almost 2 years
    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<T> returns a type with all of the keys in T marked as optional. Surrounding the Partial<T> with a Pick<...,K> gives us a type with only the keys that we supplied, which we have already made optional. Using Omit<T,K> 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
    Tim Krins almost 2 years
    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.