lonewarrior556
lonewarrior556

Reputation: 4519

Can you enforce a key on an object but let typescript deduce the type of Value?

I am trying to create an object where I would like to enforce the keys, but am happy to let typescript deduce the types of the values. A quick example is

const fooVals = {
  a: null,
  b: null,
  c: null,
  e: null,
}

type TfooVals = typeof fooVals
type JustKeysOfFooVals = { [key in keyof TfooVals]: any};

// TS deduces correct types of foo1Vals but does not let me know e is missing
const foo1Vals = {
  a: 'string',
  b: 10,
  c: Promise.resolve('string') , 
//  e: () => { console.log('bar') }
}

// lets me know 'e' is missing, but makes types any
const foo2Vals: JustKeysOfFooVals = {
  a: 'string',
  b: 10,
  c: Promise.resolve('string') , 
  e: () => { console.log('bar') }
}

TSPlayground:

Is this possible?

Upvotes: 1

Views: 349

Answers (1)

jcalz
jcalz

Reputation: 330216

I'd recommend using a generic helper function which constrains its input to a subtype of JustKeysOfFooVals and just returns its input without widening it:

const justKeysOfFooVals = <T extends JustKeysOfFooVals>(t: T)=>t;

You'd then use it like this:

const foo1Vals = justKeysOfFooVals({
  a: 'string',
  b: 10,
  c: Promise.resolve('string') , 
//  e: () => { console.log('bar') }
}); // error! property 'e' is missing

const foo2Vals = justKeysOfFooVals({
  a: 'string',
  b: 10,
  c: Promise.resolve('string') , 
  e: () => { console.log('bar') }
}); // okay
foo2Vals.e(); // okay

You get warned about missing keys and it doesn't forget about the value types. Hope that helps; good luck!

Link to code


Update: the helper function may disable excess property checks. If you need those (and you might not, after all a value {a: "a", b: "b"} is a perfectly valid instance of type {a: string}), then you can use another generic constraint to simulate exact types:

type Exactly<T, U> = T & Record<Exclude<keyof U, keyof T>, never>;
const justKeysOfFooVals = <T extends Exactly<JustKeysOfFooVals, T>>(t: T)=>t;

const foo3Vals = justKeysOfFooVals({
  a: 'string',
  b: 10,
  c: Promise.resolve('string') , 
  e: () => { console.log('bar') },
  f: 1 // error!
}); // number is not assignable to never

Link to code

Good luck again!

Upvotes: 2

Related Questions