Home > front end >  Specify the type for a key in an object
Specify the type for a key in an object

Time:10-24

I'm trying to apply a function to perform a subtraction on all the keys of objects in an array but I'm not sure how to specify the type.

function ForwardTimePointDifferenceTransformer<T>(
  array: T[],
  key: keyof T & number
) {
  array.map((value, i, array) => {
    try {
      value[key] = array[i   1][key]! - value[key]!;
      return value;
    } catch (e) {
      return value;
    }
  });

  return array;
}

It isn't allowing me to do the subtraction because the type isn't a number. How can I specify that the key must be a number?

EDIT 1 This is an example of a array I would pass through this:

[
    {
        "value": "example", 
        "numberValue": 4.498437
    },
    {
        "value": "example", 
        "numberValue": 24.3428
    },
    {
        "value": "example", 
        "numberValue": 54
    },
    {
        "value": "example", 
        "numberValue": 90
    },
    {
        "value": "example", 
        "numberValue": 100
    }
]

CodePudding user response:

It is currently not possible to do it 100% cleanly (hence the as unknown as number, see: https://github.com/microsoft/TypeScript/issues/38646), but I think the current solution ain't half bad:

// returns only keys from `T` that match type `U`
type FilteredKeys<T, U> = {
  [P in keyof T]: T[P] extends U ? P : never;
}[keyof T];

// interface from your example input
interface ForwardTimePointDifference {
  value: "string";
  numberValue: number;
}

function ForwardTimePointDifferenceTransformer<
  // ensure generic input matches shape
  T extends ForwardTimePointDifference
>(
  array: T[],
  // ensure only keys with the type of `number` can be used
  key: FilteredKeys<T, number>
) {
  array.map((value, i, array) => {
    try {
      ((value[key] as unknown) as number) = array[i   1][key]! - value[key]!;

      return value;
    } catch (e) {
      return value;
    }
  });

  return array;
}

CodePudding user response:

Here are two ways to approach this using the same runtime code, each with its own type system trade-offs:

TS Playground link


The first:

function forwardTimePointDifferenceTransformer <K extends PropertyKey>(
  array: Record<K, number>[],
  key: K,
): void {
  for (let i = 0; i < array.length - 1; i  = 1) {
    const current = array[i];
    const next = array[i   1];
    current[key] = next[key] - current[key];
  }
}

This version is simpler to write and understand, but its compiler diagnostic messages are not as helpful for problematic code. When invoked with your example array and an invalid property name, this compiler error is produced:

forwardTimePointDifferenceTransformer(array, 'value');
//                                    ^^^^^
// Argument of type '{ value: string; numberValue: number; }[]' is not assignable to parameter of type 'Record<"value", number>[]'.
//   Type '{ value: string; numberValue: number; }' is not assignable to type 'Record<"value", number>'.
//     Types of property 'value' are incompatible.
//       Type 'string' is not assignable to type 'number'.(2345)

Which leads to the second version:

type FilterKeysByValue<TObject, TValue> = keyof {
  [K in keyof TObject as TObject[K] extends TValue ? K : never]: TObject[K];
};

function forwardTimePointDifferenceTransformer <T>(
  array: T[],
  key: FilterKeysByValue<T, number>,
): void {
  for (let i = 0; i < array.length - 1; i  = 1) {
    type O = Record<typeof key, number>;
    const current = array[i] as unknown as O;
    const next = array[i   1] as unknown as O;
    current[key] = next[key] - current[key];
  }
}

This version involves more complicated types and some assertions to overcome compiler limitations, but I think the compiler diagnostics are much more useful:

forwardTimePointDifferenceTransformer(array, 'value');
//                                           ^^^^^^^
// Argument of type '"value"' is not assignable to parameter of type '"numberValue"'.(2345)

Running your example through it produces this result:

const array = [
  { value: 'example', numberValue: 4.498437 },
  { value: 'example', numberValue: 24.3428 },
  { value: 'example', numberValue: 54 },
  { value: 'example', numberValue: 90 },
  { value: 'example', numberValue: 100 },
];

console.log('before', array.map(({numberValue}) => numberValue));
forwardTimePointDifferenceTransformer(array, 'numberValue');
console.log('after', array.map(({numberValue}) => numberValue));
before [ 4.498437, 24.3428, 54, 90, 100 ]
after [ 19.844363, 29.6572, 36, 10, 100 ]
  • Related