cbdeveloper
cbdeveloper

Reputation: 31495

Types for a function that receives an object of type `[key: string]: SOME_TYPE` and returns an array of type `SOME_TYPE[]`

I have some objects containing DB documents that I constantly need to convert to arrays.

Example:

const MY_OBJECT = {
  docId_1: {...doc1},
  docId_2: {...doc2},
  docId_3: {...doc3},
  // AND SO ON
}

I need to convert it to an array like this:

const MY_ARRAY_FROM_OBJECT = [
  {...doc1}, 
  {...doc2}, 
  {...doc3},
  // AND SO ON...
]

And this is code I use to do the conversion:

function buildArrayFromObject(obj) {
  const keys = Object.keys(obj);
  const arr = [];
  for (const key of keys) {
    arr.push(obj[key]);
  }
  return arr;
}

And I need to type that function so I can use it with objects that has types like these:

interface BLOGPOSTS_ALL {
  [key: string]: BLOGPOST
}

interface PRODUCTS_ALL {
  [key: string]: PRODUCT
}

So when I call them with each different object, I want Typescript to know what the return array type will be.

For example:

const BLOGPOSTS_ALL_ARRAY = buildArrayFromObject(BLOGPOSTS_ALL);  // SHOULD BE "BLOGPOST[]"
const PRODUCTS_ALL_ARRAY = buildArrayFromObject(PRODUCTS_ALL);    // SHOULD BE "PRODUCT[]"

Here is what I've tried:

function buildArrayFromObject<T, K extends keyof T>(obj: T): T[K][] {
  const keys = Object.keys(obj);
  const arr = [];
  for (const key of keys) {
    arr.push(obj[key]);
  }
  return arr;
}

But I'm getting this error:

enter image description here

And the returning type of the function is being evaluated by Typescript as type never[]

Upvotes: 0

Views: 1034

Answers (1)

r3dst0rm
r3dst0rm

Reputation: 1926

What you could do is the following:

Create an abstraction type to define what your database handles look like:

// Create an Entity type to define how your database objects look like
type Entities<T> = { [key: string]: T | undefined }

Therefore you can express your products and blogposts like this:

type BLOGPOSTS = Entities<BLOGPOST>;
type PRODUCTS = Entities<PRODUCT>;

In order to convert them in type-safe manner into an array you can use the Object.values method provided by the JavaScript API - for more information please see MDN

It's possible to replace the method buildArrayFromObject by something like this:

const isNil = <T>(a: T | null | undefined): a is null | undefined => a === null || a === undefined;
const isAssigned = <T>(a: T | null | undefined): a is T => !isNil(a);
const entitiesToArray = <T>(entity: Entities<T>): T[] => Object.values(entity).filter(isAssigned);

This method uses the Object.values method to convert the object into an array. Afterwards it get's filtered to contain an array without undefined values. Therefore I made use of two helper methods isAssigned and isNil. Please see this CodeSandbox for an example: Code SandBox

Based on your concern, that the Object.value method is not supported by IE11, you can add a polyfill for that one. Either by pasting a polyfill in or by adding it using npm to your project. Alternatively you can replace this code by the following answer Use Object.keys to mimick Object.values

Upvotes: 1

Related Questions