Get the type of a specific key of generic type in TypeScript

853 Views Asked by At

I'm struggling to get a complex type functionality out of this updateArray generic function I am building:

// Updates an object array at the specified update key with the update value,
// if the specified test key matches the test value.
// Optionally pass testFailValue to set a default value if the test fails.
// Note that by passing a testFailValue ALL elements in the array will be updated at the specified update property. 
// If it is omitted only elements passing the test will be updated.
export const updateArray = <T, U, V>(options: {
  array: Array<T>
  testKey: keyof T
  testValue: U
  updateKey: keyof T
  updateValue: V
  testFailValue?: V
}): Array<T> => {
  const {
    array,
    testKey,
    testValue,
    updateKey,
    updateValue,
    testFailValue,
  } = options
  return array.map(item => {
    if (item[testKey] === testValue) {
      item[updateKey] = updateValue
    } else if (testFailValue !== undefined) {
      item[updateKey] = testFailValue
    }
    return item
  })
}

TypeScript will complain here in both the if statement and the two assignment statements, but in a call signature, it won't complain and is the strict type checking I am looking for exactly, ex:

interface IMyInterface {
    propertyA: string
    prepertyB: boolean
}

updateArray<IMyInterface, IMyInterface['propertyA'], IMyInterface['propertyB']>({
    array: state.editors[editor].editorSettings,
    testKey: "propertyA",
    testValue: 'someValue',
    updateKey: "propertyB",
    updateValue: true,
    testFailValue: false
})

If I omit types U and V, and replace them with T[keyof T] Typescript won't complain:

export const updateArray = <T>(options: {
  array: Array<T>
  testKey: keyof T
  testValue: T[keyof T]
  updateKey: keyof T
  updateValue: T[keyof T]
  testFailValue?: T[keyof T]
}): Array<T> => {
  const {
    array,
    testKey,
    testValue,
    updateKey,
    updateValue,
    testFailValue,
  } = options
  return array.map(item => {
    if (item[testKey] === testValue) {
      item[updateKey] = updateValue
    } else if (testFailValue !== undefined) {
      item[updateKey] = testFailValue
    }
    return item
  })
}

but this isn't entirely correct either. T[keyof T] is too flexible: I could be assigning the 'wrong' type to a given property (for example, in the example given, a boolean value to a property that should only be holding strings, or vice-versa). Obviously, this reassigning type behavior is fine in JavaScript (which is one reason why TypeScript won't complain), but undesired for this function I am crafting. Really what I need is some sort of typeof T[specific key], to ensure that testValue, updateValue, and testFailValue correspond with the right types, but the specific key can change depending on the actual type of T.

Can something like this be done?

1

There are 1 best solutions below

0
On BEST ANSWER

You can add a constraint on U so that it is a subset of the keys of T using extends. V could represent the updateKey type and also have the same constraint.

Simplifying your problem to a funtion updateObject instead of updateArray it would become:

function updateObject<
    T,
    U extends keyof T,
    V extends keyof T,
>(
    obj: T,
    testKey: U,
    testValue: T[U],
    updateKey: V,
    updateValue: T[V],
    testFailValue?: T[V]
) {
    if (obj[testKey] === testValue) {
        obj[updateKey] = updateValue;
    } else if (testFailValue !== undefined) {
        obj[updateKey] = testFailValue;
    }
}

updateObject({aString: 'hello', aNumber: 42}, 'aString', 'hello', 'aNumber', 23);