Home > Software engineering >  Typescript: Generic Function to extract only defined values from a specific key from an Array of Obj
Typescript: Generic Function to extract only defined values from a specific key from an Array of Obj

Time:11-25

I've got two functions which do exactly the same thing so it seems like a prime candidate for a generic function to replace them. However I can't quite get it right, the extracted value isn't exactly the same as the original type as the return type should be the original result/error type but now without undefined.

type PurchaseResult = {
  result: Value || undefined,
  error: Error || undefined, 
}

One of the existing examples of the function (for the other function replace key and type with result/value:

const extractErrors = (
  purchaseResults: PurchaseResult[]
) =>
  purchaseResults
    .map(({ error }) => error)
    .filter((error): error is Error => Boolean(error));

Failed Attempt


        const extractOnlyDefinedValuesFromArrayOfObjects = <
          T,
          K extends keyof A,
          A extends { [k in K]: T }
        >(
          list: A[],
          key: K
        ): T[] =>
          list
            .map((obj) => obj[key])
            .filter((value) => typeof value !== 'undefined');

        const extractedResults = extractOnlyDefinedValuesFromArrayOfObjects<
          Value,
          'result',
          PurchaseResult
        >('result', arrayOfResults);

Thanks!

CodePudding user response:

This syntax Value || undefined is not allowed, use | instead. I don't know whether you have defined type Value or not, that's why I have added T generic - for Value;

Consider this example:

type PurchaseResult<Value> = {
  result: Value | undefined,
  error: Error | undefined,
}

const extractErrors = <
  T,
  Key extends keyof PurchaseResult<T>,
  Data extends PurchaseResult<T>[]
>(
  purchaseResults: Data,
  key: Key,
) =>
  purchaseResults
    .map(elem => elem[key])
    .filter((elem): elem is NonNullable<Data[number][Key]> => Boolean(elem));

declare let results: PurchaseResult<number>[]

const result = extractErrors(results, 'result') // number[]
const result2 = extractErrors(results, 'error') // Error[]

Playground

I have used Data just to alias PurchaseResult<T>[]. Since we have appropriate generic for our list, we can use NonNullable<Data[number][Key]> for our typeguard

CodePudding user response:

A generalised version that accepts any array of key-value pairs and returns a typed array of values for the specified key, extracted from each record. (Playground)

/**
 * extractKey extracts record[key] from an array of records 
 * @param key - a key in records
 * @param records - an array of key-value records of the same type
 * @return - `key` for each record, where record[key] is defined.
*/
const extractKey = <T extends Record<string, any>, K extends keyof T>(
  key: K,
  records: T[]
): Array<NonNullable<T[K]>> => {
    return records
        .map(el => el[key])
        .filter(el => typeof el !== "undefined")
}

// define a type to test on, but can be any key-value pair type
type PurchaseResult = {
  result?: number
  error?: string
}

// some dummy data
const testMe: PurchaseResult[] = [{result: 1}, {error: "no result!"}, {result: 4}]


// test it!
const results = extractKey('result', testMe) // []number

const errors = extractKey('error', testMe) // []string
  • Related