2

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 Value = string;

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!

2 Answers 2

3

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

Sign up to request clarification or add additional context in comments.

Comments

1

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.