Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Limit object properties to keyof interface

Tags:

typescript

this code won't compile:

interface IFoo {
   a: number;
}

type TFoo = keyof IFoo;
type OFoo = { [key: TFoo]: any };

let obj: OFoo = { a: 1 };

What I'm trying to achieve is for obj to only be assignable with object with valid properties, as if I wrote:

let obj: IFoo = { a: 1};

but I need to do it by keyof.

Thanks for any ideas.

like image 407
pankleks Avatar asked Nov 08 '17 14:11

pankleks


1 Answers

While you can't directly use anything but string or number as the key type in an index signature (and your definition of OFoo will give you an error to that effect), you can restrict keys to a subtype of string using mapped types. Since TFoo, a.k.a. keyof Foo, is a subtype of string, you can use a mapped type like this:

type OFoo = { [K in TFoo]: any }; 

Note that the syntax for mapped types differs from that of regular index signatures. The TFoo above corresponds to the type of allowable keys, which is just "a" in your case, but could also be a union of string literals like "a"|"b"|"c", or even string. And the K above is a parameter which corresponds to the type of key for each property. (You don't really care about K since the type for each property value is any and has nothing to do with K, but you could have used K to pick out particular properties if you wanted to.) Anyway, let's use it:

let obj: OFoo = { a: 1 }; // okay
let obj = { }; // error, property 'a' is missing
let obj = { c: 1 }; // error, excess property 'c'

By the way, I'd probably use the built-in standard library type Record<K,T> which is defined like this:

type Record<K extends string, T> = {
  [P in K]: T
}

Or, you can think: "a Record<K, T> is an object where every key is in K and every value is a T". And for you, K is TFoo, and T is any:

type OFoo = Record<TFoo, any>;  // same thing 

Hope that helps; good luck!

like image 194
jcalz Avatar answered Oct 22 '22 19:10

jcalz