1

Try to produce subset of object

type person = {name: string, age: number, dob: Date};
type p1 = Pick<person, 'name' | 'dob'>

but get error ("error dob is missing") if

person1: p1 = {name: 'test1'}

I am wondering what is the best way to create all subsets of type person in Typescript.

Cory Kramer
  • 114,268
  • 16
  • 167
  • 218
Tim Woohoo
  • 470
  • 1
  • 7
  • 17
  • Hello. But the error is correct, you are missing `dob` field because this is how you described it in the Pick. `'name' | 'dob'` doesnt mean that only one of those fields is required. – kind user Nov 11 '22 at 13:08
  • well, I mean the question should be like how to create all subsets of a type in Typescript – Tim Woohoo Nov 11 '22 at 13:10
  • Sounds like you are looking to make the properties optional. If so, you can use the Optional type in addition to Pick or you can make the fields in the original type optional. – David T. Nov 11 '22 at 13:13
  • I've tried them but the problem is if broader type (eg. {wild: 'somethingWild'}) is assigned to the optional type, no warning or error will show as object keys are all optional. – Tim Woohoo Nov 11 '22 at 14:18

2 Answers2

2

Is this what you want?

type person = {name: string, age: number, dob: Date};
type PickOptional<T, K extends keyof T> = {[k in K]?:T[k]}

type Test = PickOptional<person,'name' | 'dob'>
let person1: Test = {name: 'test1'}
Giorgi Moniava
  • 27,046
  • 9
  • 53
  • 90
  • Thanks for ur answer. Although all keys are optional but if person1: Test = { na: 'test1'}, no type warning showing. This is same as Partial which will no complain {} or something else. I expect the type warning shows when something other than subsets of the person type is assigned. for example, person1: Test = { na: 'test1'} result in error or warning – Tim Woohoo Nov 11 '22 at 14:23
  • @TimWoohoo hm `person1: Test = { na: 'test1'}` does give me [error](https://www.typescriptlang.org/play?ssl=5&ssc=35&pln=1&pc=1#code/C4TwDgpgBJBODOB7AdlAvFA3sghgWwgC4p5hYBLZAcwBoocqipkBXPAIwljoBNF3iAERzAIAXwDcAKFCQoABXIBjANYB5MMHIocAGwA8AFToBpKBAAeo5D3hQVEEIgBmUQwD50WANoqolKBMAXQB+QkNfILEpGXBoQwhSL0VVDS0dAzgkZBoAclwCXKgAHyhcvnZc9yldCGAYLmyARmIEpIxMZhxiXNFSJtyxIA) – Giorgi Moniava Nov 11 '22 at 14:42
0

The normal Pick utility type is probably not what you want as it produces a type containing all the properties specified in the union of keys. We can make our custom PickUnion which behaves differently.

type PickUnion<T, K extends keyof T> = 
  K extends K
    ? Pick<T, K>
    : never

It works by distributing the result over K which leads to a union of Picks.

type P1 = PickUnion<Person, 'name' | 'dob'> & Partial<Person>

// valid
const person1: P1 = { name: 'test1' }
const person2: P1 = { dob: new Date() }

// invalid
const person3: P1 = {}

Playground

Tobias S.
  • 21,159
  • 4
  • 27
  • 45
  • Just curious new to TS. do you use `K extends K` as a trick to traverse union? – Giorgi Moniava Nov 11 '22 at 14:34
  • 1
    @GiorgiMoniava - yes, its called distributive conditional types – Tobias S. Nov 11 '22 at 14:35
  • 1
    @TobiasS. followed up question, is possible to remove the error from person3.dob in [here](https://www.typescriptlang.org/play?ts=4.5.5#code/C4TwDgpgBAChBOBnA9gOygXigb1QQwFsIAuKRYeAS1QHMAaKPGkqVAVwICMEGATZTqQAieYBAC+AbgBQoSLEoBjANYBVVJTQAeACoMA0lAgAPMal6IoyiCGQAzKDoB8mKNKhRDJsxc-uPUAD8Ciq6Bk7+HqSoEABuCNKy4NAwAIyuMEpqGtpwSGgMAOT4RIVQAD5QhfychRHSAPQNULF4ADaUvNKKaORQkPmoqaRprtishCyFYuSpZeLdvcD9CCioAEwj6VjjNdEQAO5QImIAFACUUAuNzdStHV09qH0DawDMW2MlUzPAc3wCfZHE4QC4ea6vNBvAB0NSAA) – Tim Woohoo Nov 11 '22 at 15:37
  • even though `const person3: P1 = {name: 'test1', dob: new Date() }`, `person3.dob` is in error as dob does not exist in `P1` – Tim Woohoo Nov 11 '22 at 15:40
  • @TimWoohoo - you can intersect the type with `Partial` – Tobias S. Nov 11 '22 at 15:45
  • https://tsplay.dev/mMB3rW – Tobias S. Nov 11 '22 at 15:47
  • @TobiasS. Isn't a bit weird that TS *allowed* `const person3: P1 = {name: 'test1', dob: new Date() }`, and *then* complained about accessing `dob` ? – Giorgi Moniava Nov 11 '22 at 16:45
  • @GiorgiMoniava - No, TypeScript has a *structural type system* where *excess properties* are (almost always) allowed. There are only certain places where the compiler will give an error on excess properties. Assigning an object literal is one of those places. But even though we are assigning an object literal here, there is no check because the type is a union. – Tobias S. Nov 11 '22 at 16:48
  • @TobiasS. I know that I meant it feels unintuitive that language *allowed* the assignment, and *then* complained when I used the property. – Giorgi Moniava Nov 11 '22 at 16:50
  • @GiorgiMoniava - its often the case that the type of a variable has *less* information about the actual value than it could have. Let's say `let a: object = { a: "123" }`. Then `a.a` would be an error too. The type simply does not have any information about what is assigned to the variable. You can only access a property if the type is guaranteed to have it (or if it is optional) – Tobias S. Nov 11 '22 at 16:52
  • @TobiasS. I think you refer to [such](http://shorturl.at/ijtu7) case. Also I found [this](https://github.com/microsoft/TypeScript/issues/36945#issuecomment-589973055) by TS dev, which I think explains that this is allowed not due to excess property checks. Just wanted to share. I know SO [answer](https://stackoverflow.com/a/65805753/3963067) which on the other hand explains that in terms of excess property checks. – Giorgi Moniava Nov 11 '22 at 16:59
  • @GiorgiMoniava - yes exactly – Tobias S. Nov 11 '22 at 17:00
  • @TobiasS. OK I see just that feels bit weird to me but OK :). ps. don't you find those two links contradicting in explanation? – Giorgi Moniava Nov 11 '22 at 17:02
  • @GiorgiMoniava - no not really. What do you find contradicting? – Tobias S. Nov 11 '22 at 17:03
  • @TobiasS. Yeah I don't know I might have misread it. I thought github link was saying it had nothing to do with excess prop checking. – Giorgi Moniava Nov 11 '22 at 17:19