Home > Mobile >  Typescript Union Type Record Or Array Of Record
Typescript Union Type Record Or Array Of Record

Time:04-12

I Have a method which takes a single parameter which is a union type of obj: QueryResultRow | readonly QueryResultRow[]. Since this can be either type of QueryResultRow or QueryResultRow array I need a way to determin which is which. I can do that via Array.isArray(obj) but the typescript compiler does not recognise that after that point it is the single record and throws an error. My method is

protected rowMapper(obj: QueryResultRow | readonly QueryResultRow[])
{
    if (Array.isArray(obj))
    {
        return obj.map((row) => new Contact(row.id, row.name, row.email, row.phone_number, row.creation_date));
    }

    return new Contact(obj.id, obj.name, obj.email, obj.phone_number, obj.creation_date);
}

The error I get is TS2339: Property 'id' does not exist on type 'QueryResultRow | readonly QueryResultRow[]'.   Property 'id' does not exist on type 'readonly QueryResultRow[]'. because it doesnt know its the single record from the point after the if statement. There is no problem in funcitonality because I can guarantee what it is but I would like the typescript compiler to know.

CodePudding user response:

Notes:

  1. Array.isArray() does not work for ReadonlyArray - see #17002: Array.isArray type narrows to any[] for ReadonlyArray
  2. you should use an else clause

Example code for typescript 4.6.2:

type QueryResultRow = {
  id: string
}

function foo(obj: QueryResultRow | Array<QueryResultRow>) {
  if (Array.isArray(obj)) {
    return obj.map(row => row.id);
  } else {
    // typescript knows that obj is of type QueryResultRow in the else branch
    return obj.id;
  }
}

function fooReadonly(obj: QueryResultRow | ReadonlyArray<QueryResultRow>) {
  if (Array.isArray(obj)) {
    // note: obj is of type any[]
    return obj.map(row => row.id);
  } else {
    /**
     * ERROR: Property 'id' does not exist on type 'QueryResultRow | readonly QueryResultRow[]'.
     *   Property 'id' does not exist on type 'readonly QueryResultRow[]'.
     */
    return obj.id;
  }
}

The linked issue mentions some workarounds.
I'd just use a simple type-guard in this case:

/**
 * type guard is needed until this bug is fixed:
 * https://github.com/microsoft/TypeScript/issues/17002
 */
function isNotArray<T>(value: T | ArrayLike<T>): value is T {
  return !Array.isArray(value);
}

function fooReadonlyFixed(obj: QueryResultRow | ReadonlyArray<QueryResultRow>) {
  if (isNotArray(obj)) {
    return obj.id;
  } else {
    return obj.map(row => row.id);
  }
}

function fooFixed(obj: QueryResultRow | Array<QueryResultRow>) {
  if (isNotArray(obj)) {
    return obj.id;
  } else {
    return obj.map(row => row.id);
  }
}

note, that we have inverted the logic (isNotArray instead of isArray), so that it works correctly for Array<> and ReadonlyArray<>

Stackblitz example

  • Related