Home > Back-end >  How to access nested type of: K extends keyof T, T[K], e.g. T[K]["someSubField"]
How to access nested type of: K extends keyof T, T[K], e.g. T[K]["someSubField"]

Time:08-18

I have the following function:

const fieldDefinitions = {
  a: { id: "14", "default": "none" },
  b: { id: "15", "default": 0 },
  c: { id: "16", "default": 0 },
};

const setFields = <T extends typeof fieldDefinitions, K extends keyof T>(fieldValues: { fieldName: K, value: T[K]["default"] }[]) => {
  //logic to set fields
}

I want to type it in a way that it will only accept field values that are of the same type as the default value.

E.g.:

setFields([
  { fieldName: "a", value: "somestring" },
  { fieldName: "c", value: 123 },
])
//should work because:
// - default of a is "none" which is a string
// - default of c is 0 which is a number


setFields([
  { fieldName: "a", value: 123 },
  { fieldName: "c", value: "somestring" },
])
//should NOT work because:
// - default of a is "none" which is a string, but a number is given
// - default of c is 0 which is a number, but a string is given

Here is where I get the error in the typedefinition:

value: T[K]["default"]

It says: Type '"default"' cannot be used to index type 'T[K]'

So my question is, what is the right way to access the type of the "default" property of T[K] here?

Here is the code in TypeScript Playground:

CodePudding user response:

If T extends typeof fieldDefinitions that means it could be any subtype that has any other properties that do not have an object type with default.

T is not really used, you only want fieldDefinitions not a subtype of it, so you could do the following:


type FieldDefinitions = typeof fieldDefinitions
/**
 * The field value should be enforced to be the same type as the default value in the definition
 */
const setFields = <K extends keyof FieldDefinitions>(fieldValues: { fieldName: K, value: FieldDefinitions[K]["default"] }[]) => {
  //logic to set fields
}

Playground Link

However this will not actually solve the problem you are trying to solve. It will not ensure that in an element of the array, the key and value are correlated (the value will be typed as a union of all present keys)

The real solution here is to use a mapped type as the parameter. This technique, sometimes called a reversed mapped type, will ensure TS can follow the correlation inside each element of the array:

const fieldDefinitions = {
  a: { id: "14", "default": "none" },
  b: { id: "15", "default": 0 },
  c: { id: "16", "default": 0 },
};

type FieldValues<T> = {
    [P in keyof T]: { fieldName: T[P], value: typeof fieldDefinitions[T[P] & FieldDefinitionKeys]['default'] }
}

type FieldDefinitionKeys = keyof typeof fieldDefinitions
const setFields = <T extends [FieldDefinitionKeys] | FieldDefinitionKeys[]>(fieldValues: FieldValues<T>) => {
}



// works
setFields([
  { fieldName: "a", value: "somestring" },
  { fieldName: "c", value: 123 },
])

//does not work
setFields([
  { fieldName: "a", value: 123 },
  { fieldName: "c", value: "somestring" },
])

Playground Link

  • Related