How to change type of index expression? ie: c[k]

56 Views Asked by At

My Code

import * as R from 'ramda';

import { ILPAsset } from 'shared/types/models';

interface TextMatchFunction {
  (part: string, typed: string): boolean;
}

const textMatch: TextMatchFunction = (part: string, typed: string) => typed.search(part) !== -1;

export const filterAssets = (txt: string, assets: ILPAsset[]): ILPAsset[] => {
  const checkText = (k: string, c: keyof ILPAsset) => (textMatch(txt, c[k].toLowerCase()) ? c : null);
  const curriedCheckText = R.curry(checkText);
  // @ts-ignore
  const bySymbol = R.map(curriedCheckText('symbol'), assets);
  return R.reject(R.isNil, bySymbol);
};

IPAsset's interface

export interface ILPAsset {
  symbol: string;
  lastPayout: number;
  historical: number;
}

Problem is on this line:

const checkText = (k: string, c: keyof ILPAsset) => (textMatch(txt, c[k].toLowerCase()) ? c : null);

Typescript expects k to be a number c[k], when it's in fact a key for an object in ILPAsset, which is string that in my case will be symbol.

How would this be handled in Typescript?

UPDATE

A much simpler approach to do this btw, however I got a great answer for future issues in regards to key checking :D

export const filterAssets = (typed: string, assets: ILPAsset[]): ILPAsset[] => {
  const checkSymbol = (asset: ILPAsset) => 
    asset.symbol.includes(typed.toUpperCase());
  return R.filter(checkSymbol, assets);
};
1

There are 1 best solutions below

2
On BEST ANSWER

The problem is caused because you are using k as the key to c. Since you mention you expect k to be a keyof ILPAsset that would mean c should be ILPAsset. So the signature should be:

const checkText = (k: keyof ILPAsset, c: ILPAsset) => (textMatch(txt, c[k].toLowerCase()) ? c : null);

The left over problem is that now the index access c[k] will not be of type string since ILPAsset contains both number and string keys.

We have two solutions for this.

We could check if c[k] is a string and if it not return null:

const checkText = (k: keyof ILPAsset, c: ILPAsset)  => {
  const v = c[k];

  return typeof v === 'string' ? (textMatch(txt, v.toLowerCase()) ? c : null): null;
} 

We could also filter the keys so k can only be a key taht would be a string

type StringKeys<T> = { [P in keyof T] : T[P] extends string ? P: never}[keyof T]
const checkText = (k: StringKeys<ILPAsset>, c: ILPAsset)  => (textMatch(txt, c[k].toLowerCase()) ? c : null);

Note: The only string key of ILPAsset is symbol so perhaps you should evaluate the need for the k parameter at all. Why not just access c.symbol?