Home > Blockchain >  How to assert a value is a valid key without knowing the object keys at compile-time?
How to assert a value is a valid key without knowing the object keys at compile-time?

Time:01-21

In TypeScript, is there any way to declare that a variable is a keyof some Record without knowing what the keys of the Record are?

Assume an API response returns a payload of JSON. I want to define a type for the keys of the payload so I can index through the payload and know that the value will not be undefined. Is this possible or do I have to simply check for undefined every time I index the payload object?

Example:

const payload: Record<string, ValidValue> = await myApiCall()

const prop1 = 'a'
const prop2 = 'b'

if (isValidKey(prop1, payload)) {
  const value1 = payload[prop1] // the type of `value1` should evaluate to `ValidValue`
}

const value2 = payload[prop2] // the type of `value2` should evaluate to `ValidValue | undefined`

CodePudding user response:

I got it to work using a type guard function as well, but not returning key is keyof MyMap. Instead, I had a type for the payload object of Record<string, ValidValue | undefined> and then the type guard returns payload is Record<K, ValidValue> where K is the type of the key param.

You can play with it on TS Playground.

// Type alias for convenience
type Payload = Record<string, ValidValue | undefined>;

const payload: Payload = await myApiCall()

function isValidKey<K extends string>(key: K, payload: Payload): payload is Record<K, ValidValue> {
  return key in payload;
}

const prop1 = 'a'
const prop2 = 'b'

if (isValidKey(prop1, payload)) {
  const value1 = payload[prop1] // the type of `value1` should evaluate to `ValidValue` ✅
}

const value2 = payload[prop2] // the type of `value2` should evaluate to `ValidValue | undefined` ✅

CodePudding user response:

I think I found a solution. This appears to work as expected in the TS playground.

const myMap = [1,2,3].reduce((p, n) => ({ ...p, [Math.random()]: n }), {})

type MyMap = typeof myMap

console.log('myMap', myMap)

function isKnownKey(key: string, thing: MyMap): key is keyof MyMap {
    return Object.keys(thing).includes(key)
}

const knownTypes = ['a', 'b']
const prop1 = Object.keys(myMap)[0]
const prop2 = 'b'

if (isKnownKey(prop1, myMap)) {
    const value1 = myMap[prop1]
    console.log('value1', value1)
}

const value2 = myMap[prop2]
/*
TS Error:
Element implicitly has an 'any' type because expression of type '"b"' can't be used to index type '{}'.
  Property 'b' does not exist on type '{}'.(7053)
*/

However, one thing is concerning and that I don't understand: value1 evaluates to type never. Yet there's no TS error. What's going on here?

  • Related