Skip to content
Advertisement

Typescript returning array of objects with unknown keys

I’m new to TypeScript, and trying to slowly migrate an existing JS (react) project to TypeScript.

I have a function executing a query:

/**
 * Run a Query against the currently connected database
 * @param {String} sql Query to run
 * @param {Object[]} Array of objects to be used in the prepared statement
 * @returns {Promise<Object[]>} Promise resolving after query is done. Returns an array of arrays with the result.
 */
const query = (sql, params) => {
  //Database stuff
}

And I’m calling this in numerous locations:

  const result = await query('SELECT count(*) as cnt from Table id=?',[2]')
  return result[0].cnt

The last line gives an error, because the field ‘cnt’ is unknown. How to best solve this? The return type is always Object[], but the content ob the object depends on the query.

Any suggestions how to best move this code to TS?

Advertisement

Answer

This is one of those places where the type system has to just be told what you’re expecting. Naturally, you have to take great care when doing that to ensure you’re giving the type system accurate information. Typically you want to do this as early as possible so that the more complex code built on it can rely on the types.

So an answer in two parts:

  1. How to type query, and

  2. A suggestion for avoiding using query directly

First, you can make query a generic function by giving it a generic type parameter to tell it what kinds of objects it returns:

/**
 * Run a Query against the currently connected database
 * @param {string} sql Query to run
 * @param {any[]} Array of objects to be used in the prepared statement
 * @returns {Promise<ResultType[]>} Promise resolving after query is done. Returns an array of arrays with the result.
 */
const query = <ResultType extends object>(sql: string, params: any[]): Promise<ResultType[]> => {
// −−−−−−−−−−−^^^^^^^^^^^^^^^^^^^^^^^^^^^−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−^^^^^^^^^^^^^^^^^^^^^
    //Database stuff
};

Then for example you could use it like this:

const result = await query<{cnt: number}>('SELECT count(*) as cnt from Table id=?', [2]);
// −−−−−−−−−−−−−−−−−−−−−−−−^^^^^^^^^^^^^
return result[0].cnt;

But here’s where #2 comes in: I wouldn’t do that except in a function you define once whose job is to tell you how many rows match:

async function getCountFromExampleTable(id: number): Promise<number> {
    const result = await query<{cnt: number}>('SELECT count(*) as cnt from Table id=?', [id]);
    return result[0].cnt;
}

It’s really easy to look at getCountFromExampleTable and know that the type argument we’re giving ({cnt: number}) is correct. Then any code that needs to do this can reuse getCountFromExampleTable rather than having to provide that type argument (and possibly get it wrong).

You’d probably want getCountFromExampleTable to be more generic, perhaps accepting the table name and the parameters, but you get the idea. (If so, be sure to test the table name to ensure it can’t be used as part of an SQL injection attack, and continue to use a prepared statement as you already are.)

Playground link

User contributions licensed under: CC BY-SA
6 People found this is helpful
Advertisement