简体   繁体   中英

Type for a function that gets an object of type "{ [key: string]: TYPE_X }" and needs to return an array of "TYPE_X[]"

This is the code:

Typescript playground

interface TYPE_1 { foo1: "bar1" }
interface TYPE_2 { foo2: "bar2" }

interface OBJECT_1 {
    [key: string]: TYPE_1
}

interface OBJECT_2 {
    [key: string]: TYPE_2
}

type ARRAY_1 = TYPE_1[];
type ARRAY_2 = TYPE_2[];

type OBJ_IN = OBJECT_1 | OBJECT_2
type ARRAY_OUT = ARRAY_1 | ARRAY_2

function buildArrayFromObject(obj: OBJECT_1): ARRAY_1
function buildArrayFromObject(obj: OBJECT_2): ARRAY_2
function buildArrayFromObject(obj: OBJ_IN): ARRAY_OUT {
  const arr = [] as ARRAY_OUT;
  for (const item in obj) {
    arr.push(obj[item]);
  }
  return arr;
}

const obj1: OBJECT_1 = { id1: {foo1: "bar1" }, id2: {foo1: "bar1"}};
const obj2: OBJECT_2 = { id1: {foo2: "bar2" }, id2: {foo2: "bar2"}};

const array1 = buildArrayFromObject(obj1);
const array2 = buildArrayFromObject(obj2);

This is the error I'm getting:

在此处输入图片说明

在此处输入图片说明

This code needs to accomplish the following:

  • Take an object of type { [key: string]: TYPE_X } and return an array of TYPE_X[]

QUESTION

JS-wise the code is doing what it needs to. But Typescript is complaining about the types that are going into the array. What am I doing wrong? How can I fix it?

Problem

With this sort of overload structure there's no relationship between the "in" and the "out." You're trying to declare that the array elements much match the expected "out" type with as ARRAY_OUT , but you don't actually know what the expected type is. Recall that ARRAY_OUT is just an alias for ARRAY_1 | ARRAY_2 ARRAY_1 | ARRAY_2 . So when you write:

const arr = [] as ARRAY_OUT;

You're saying that this array is either ARRAY_1 or ARRAY_2 .

The only way you can safely push to a union of two array types is if the value that you are adding could go in both array types, ie. it must be TYPE_1 & TYPE_2 .

But the value that you get from your object is of either TYPE_1 | TYPE_2 TYPE_1 | TYPE_2 and you don't know which. So you get the error "Argument of type 'TYPE_1 | TYPE_2' is not assignable to parameter of type 'TYPE_1 & TYPE_2'"

Solution

We can use generics so that the function understands the pairing between "in" and "out" values. Instead of defining ARRAY_OUT as a union, define it as a generic with depends on the value of the input object.

We know that the returned array is an array whose elements are the values of the input object:

type Values<T> = T[keyof T];

type ARRAY_OUT<T> = Values<T>[];

So the function becomes:

function buildArrayFromObject<T extends OBJ_IN>(obj: T): ARRAY_OUT<T> {
  const arr = [] as ARRAY_OUT<T>;
  for (const item in obj) {
    arr.push(obj[item]);
  }
  return arr;
}

(note: ARRAY_OUT<T> is the same as (T[keyof T])[] and you can use either one as your return type )

The reason this works while the overloads didn't is because T is the same for both the "in" and "out".

Typescript Playground Link

With the help of @Linda's answer, this is what I've come up with:

type GENERIC_OBJECT<T> = {
  [key: string] : T
}

export function buildArray<T>(obj: GENERIC_OBJECT<T>) : T[] {
  const arr = [];
  for (const item in obj) {
    arr.push(obj[item]);
  }
  return arr;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM