0

I want to map over union of arrays, how can I make it type safely?

I have a solution, but I want to avoid hard-coding the types from the type union. When calculating newArr1, as expected, I get an error: Property 'field2' does not exist on type 'Common<A, B>' Is there a way to get the same error when calculating newArr2?

(link to code)

interface A {
    field1: string
    field2: number
}
interface B {
    field1: string
}

type Common<A, B> = {
    [P in keyof A & keyof B]: A[P] | B[P];
}

function mapArray(arr: A[] | B[]) {

    const newArr1 = (arr as Common<A, B>[]).map(i => i.field2) // error

    // how can I get the same error without hard-coding the types from the type union?
    const newArr2 = (arr as MagicEventuallyFromJcalz<typeof arr>).map(i => i.field2)

    return newArr1
}
awho
  • 431
  • 5
  • 13

1 Answers1

0

Thanks jcalz and William Lohan!

A real use case: for 2 different requests from an external api, I get 2 different responses:

  1. an array with users having many details about each user, and
  2. an array with users having a small subset of the details from the previous point

Using the same function which parses either one of the 2 array from above, I want to get the users' ids (id is a common property of users from both arrays), 1) in a type safe manner and 2) without hard-coding the array types in function body. Based on your comments jcalz, I was able to achive that. You deserve the credit for the solution, if you post an answer, I'll mark it as accepted.

The solution I found (link to code):

interface DetailedUser {
    id: number
    many: number
    other: string
    fields: boolean
}
interface CompactUser {
    id: number
}

type Common<T> = Pick<T, keyof T>

type UnpackedFromArray<T> = T extends (infer U)[] ? U : never

function getUserIdsFromApi(usersJsonFromApi: DetailedUser[] | CompactUser[]) {

    // works as expected: in both lines below, a ts error is raised
    // note that the types (DetailedUser[], CompactUser[]) are not hard-coded, but determined from variable usersJsonFromApi

    const userIds1 = (usersJsonFromApi as Common<UnpackedFromArray<typeof usersJsonFromApi>>[]).map(i => i.other)

    const userIds2 = (usersJsonFromApi as Common<typeof usersJsonFromApi[number]>[]).map(i => i.other)

    return userIds1
}
awho
  • 431
  • 5
  • 13
  • 1
    I generally prefer `type UnpackedFromArray> = T[number]` (not that you need to give it a name for such a short thing) to the conditional type `T extends (infer U)[] ? U : never` since the former, a lookup type, is easier for the compiler to reason about. But they both work. – jcalz May 30 '19 at 00:20