0

I wrote a code which map QueryArrayResult to object in deno using typescript.

const mapDbResultToModelArray = <T>(dbResult: QueryArrayResult<Array<string>>): Array<T> => {
    const objArr: Array<T> = [];
    for (let i = 0; i < (dbResult.rowCount || 0); i++) {
        const obj = {} as T;
        for (let columnInfo of (dbResult.rowDescription || {columns:[]}).columns) {
            //@ts-ignore
            obj[columnInfo.name] = dbResult.rows[i][columnInfo.index - 1];
        }
        objArr[i] = obj;
    }
    return objArr;
}

But this code has //@ts-ignore and problem which I can't expect the type of return obj is T. The Object can have null value

How to assign safely a value with the dynamic property name like columnInfo.name without //@ts-ignore

yeop
  • 29
  • 7
  • 1
    Why even bother using a generic? This code is appears coupled to a specific query result? Just create a specific concrete type and use it. Why do you parameterize `QueryResultArray` with `any` and then ask about safety? I'm trying and failing to wrap my head around what you're after here... – Jared Smith Mar 18 '21 at 02:08
  • umm... I just saw a post which assign the value to his object with Literal Types. So, I wrote the function using general. Lastly, I used QueryArrayResult becuase I just copied sorry. I'll change to string to any. – yeop Mar 18 '21 at 02:30
  • Additionally, I just want to know assigning some value to object properties when the name of property is dynamic. – yeop Mar 18 '21 at 02:34
  • 1
    Duplicate of https://stackoverflow.com/questions/12710905/how-do-i-dynamically-assign-properties-to-an-object-in-typescript – Jared Smith Mar 18 '21 at 11:29

1 Answers1

1

If you simply want to bypass the ts-ignore here are a few things you can do

  1. use a function that bypasses type checking
for (let columnInfo of (dbResult.rowDescription || {columns:[]}).columns) {
   Object.assign(obj, {[columnInfo.name] : dbResult.rows[i][columnInfo.index - 1]});
}

  1. cast as keyof T
for (let columnInfo of (dbResult.rowDescription || {columns:[]}).columns) {
   obj[columnInfo.name as keyof T] = dbResult.rows[i][columnInfo.index - 1];
}
  1. since you casted the array as Array<T>, you don't need to cast obj as T
 const obj: any = {};
 for (let columnInfo of (dbResult.rowDescription || {columns:[]}).columns) {
   obj[columnInfo.name] = dbResult.rows[i][columnInfo.index - 1];
 }
 objArr[i] = obj;

To explain what is happening... Current there are no restrictions on T, saying <T> T can be a string, number, object, Symbol.. etc.. you can add restrictions on T by using extend ie. <T extends object> would ensure that T would descend from type object. Note: this would not fix your issue. extends is not the same as equals. ie.. In your original function doing

type T = Record<string, any>
const obj = {} as T;

would solve your issues aswell. However doing

const mapDbResultToModelArray = <T extends Record<string, any>>(dbResult: QueryArrayResult<Array<string>>): Array<T> => {
    const objArr: Array<T> = [];
    for (let i = 0; i < (dbResult.rowCount || 0); i++) {
        const obj = {} as T;

would not. You would still have an indexing error.. That is because in the former exampe T really is Record<string, any> but in the ladder example T only inherits from Record<string, any> but could still be of a more specific type ie. T = Record<'key1', 'key2', any>

this is why casting obj[columnInfo.name as keyof T] will always work because if the keys are string then keyof T will be string but if the keys are 'key1' | 'key2' then keyof T will be key1' | 'key2

So even if you typed it out better you could still have issues but hope you would understand them better.

When I got started with TS things like this did seem very annoying, but over time usually investigating issues like this helps you understand the code more deeply. ie. even though since you wrote the code and you know what is happening, this will likely be correct at run time, but ts will highlight what could possibly go wrong if minor mistakes are made.

So do not be discouraged from asking to get a deeper understanding.

lonewarrior556
  • 3,917
  • 2
  • 26
  • 55
  • Thank you. It is the solution I want to know. Can you explain why there is an error which is `Type 'string' is not assignable to type 'T[keyof T]'` in the first but not in the second? – yeop Mar 18 '21 at 02:40
  • @yeop because a string could be *anything*, whereas the keys of a typed object are a specific set of strings. – Jared Smith Mar 18 '21 at 11:21
  • 2
    This answer completely abandons type safety. It "fixes" the problem in the same way that ripping a seat belt out of a car "fixes" the problem of you not being able to get it buckled. It may be the best way if OP is very certain that query result isn't going to change, but you may want to at least mention it's not type safe. See for example the linked duplicate in the comments on the question. – Jared Smith Mar 18 '21 at 11:28
  • 1
    @Jared Smith agreed, but sometimes it's best to help someone get one step further such that he gets an understanding over time vs him breaking his computer in two and putting the keyboard throu the monintor – lonewarrior556 Mar 18 '21 at 13:26
  • 2
    @lonewarrior556 totally fair, and I didn't downvote your answer or anything, I just think it's at least worth mentioning that this is Not The Way You Do It TM. – Jared Smith Mar 18 '21 at 15:38
  • Thank you, guys. I just have learned deno library and even typescript from Feb. So, I glade to your any answers and comments. Like lonewarrior556 says `So do not be discouraged from asking to get a deeper understanding.`, I'll try to learn. – yeop Mar 19 '21 at 01:20