1

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?

2
  • unknown type might be handy here Commented Jun 17, 2019 at 20:35
  • makes all the types unknown. Commented Jun 18, 2019 at 16:37

1 Answer 1

2

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!

Sign up to request clarification or add additional context in comments.

1 Comment

Yea, it does seem like this is the only way, although there should be another. Will open an issue

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.