Jeanluca Scaljeri
Jeanluca Scaljeri

Reputation: 29109

Define a list of optional keys for Typescript Record

I want to type an object which can only have keys 'a', 'b' or 'c'.

So I can do it as follows:

Interface IList {
    a?: string;
    b?: string;
    c?: string;
}

They are all optional! Now I was wondering if this can be written with Record in just one line

type List = Record<'a' | 'b' | 'c', string>;

The only issue is that all keys need to be defined. So I ended up with

type List = Partial<Record<'a' | 'b' | 'c', string>>;

This works, but I can imagine there is a better way to do this without Partial. Is there an other way to make the keys optional inside Record ?

Upvotes: 155

Views: 121478

Answers (8)

cstff
cstff

Reputation: 163

Check if this meet your requirements:

type SomeType = Partial<Record<keyof IList, string>>

You can update IList to define new valid keys. And they will be optional.

Upvotes: 2

Gianluca Casati
Gianluca Casati

Reputation: 3753

You can also use

type Key = "a" | "b" | "c"

type List = { [key in K]?: string }

Upvotes: 4

Aquazi
Aquazi

Reputation: 647

It should be noted that all (valid) solutions to the question also allow empty Records to be passed. If you want to enforce at least a specific key you need:

interface Foo {
  a: A,
  [key: string]: B
}

Upvotes: 2

Danny Pule
Danny Pule

Reputation: 1130

It's also possible to do it this way:

type List = { [P in 'a' | 'b' | 'c']?: string }; // The `?` makes the keys optional

Examples:

const validList1: List = {
  a: 'hi'
}

const validList2: List = {
  b: 'hi',
  c: 'there'
}

const validList3: List = {
  a: 'oh',
  b: 'hi',
  c: 'there'
}

Upvotes: 15

Alex Tarasenko
Alex Tarasenko

Reputation: 789

Looks like in new versions of typescript you may do the following

type YourUnion = 'a' | 'b' | 'c';   
type ObjectWithOptionalKeys = Partial<Record<YourUnion, string>>
const someObject: ObjectWithOptionalKeys {
  a: 'str', // works
  b: 1 // throws
}
// c may not be specified at all

Upvotes: 34

bvdb
bvdb

Reputation: 24710

Aside from the Partial<Record<List, string>> solution, there is perhaps a more obvious option to consider.

Instead, you could store your data in a map.

const map: Map<KeyType, ValueType> = new Map();

From a functional point of view there's not much difference. It really depends on the context whether this is a viable alternative or not.

Upvotes: 7

Fenton
Fenton

Reputation: 250882

You can create the partial version of your List type:

type PartialList = Partial<List>;

And you could do it all on one line if you don't want the intermediate type:

type PartialList = Partial<Record<'a' | 'b' | 'c', string>>;

You might decide that, in the end, the most expressive for your future self is:

type List = {
    a?: string;
    b?: string;
    c?: string;
}

Upvotes: 85

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249536

There is no way to specify the optionality of members of Record. They are required by definition

type Record<K extends keyof any, T> = {
    [P in K]: T; // Mapped properties are not optional, and it's not a homomorphic mapped type so it can't come from anywhere else.
};

You can define your own type if this is a common scenario for you:

type PartialRecord<K extends keyof any, T> = {
  [P in K]?: T;
};
type List =  PartialRecord<'a' | 'b' | 'c', string>

Or you can define PartialRecord using the predefined mapped types as well:

type PartialRecord<K extends keyof any, T> =  Partial<Record<K, T>>

Upvotes: 198

Related Questions