0

I am trying to get typescript generic to work when it is being used to access an object's property dynamically (inside square brackets).

**Minimal Example : **

export type TaskType = 'withdraw' | 'pickup'

interface Task<T extends TaskType = TaskType> {
  val: number
}

interface Memory {
  tasks : {
    [T in TaskType] : {
      [targetId: string]: Task<T>
    }
  }
}

const data: Memory = {
  tasks : {
    withdraw : {
      'a' : {val:  0}
    },
    pickup : {
      'a' : {val: 0}
    }
  }
}

const res1 =  data.tasks.withdraw.a; // Type is set correctly to Task<"withdraw">


let fn = function<T extends TaskType>(taskType: T) {
  const res2 = data.tasks[taskType].a; 
  // ^ Type here is not being infered. It is Task<"withdraw"> | Task<"pickup">
};

Link to TS Playground

As you can see in the above example when called outside the function with a specific key res1 returns the correct type.

But when using a variable data.tasks[taskType].a; it dosent work. Type of res2 should be Task<T>, instead it is something else. What am I doing wrong ?

UPDATE : More Info

I feel like I should expand a bit more on what my actual problem is. The actual Task interface does not overlap between different TaskTypes. Also I have a function like the one shown below.

interface Task<T extends TaskType = TaskType> {
  val: number
  resourceType: T extends 'withdraw' ? string : undefined
}
...
let claim = <T extends TaskType>(taskType: T, task: Task<T>) => {
  data.tasks[taskType].a = task;  // Why is task not assignable here ?
}

Link to TS Playground

Since Task<T> between various string do no have the same properties, the line data.tasks[taskType].a = task; throws an error.

I am calling the claim function like so :

const pickupTask = {val: 0, resourceType: undefined} as Task<`pickup`>;
claim('pickup', pickupTask) // The function will always be called with the same tasktype i.e. calling claim('withdraw', pickupTask) should throw an error

Logically speaking the parameter pickupTask should be able to be assigned to data.tasks[taskType].a since taskType='pickup'. So why is it throwing an error ? I might have misunderstood typescript generics, but I am not sure where I am wrong.

Also I cannot change Memory interface to be a generic interface.

6
  • 2
    I don't get the point. It's Task<withdraw> | Task<pickup because T is withdraw|pickup. It's not enforced to be a specific one. Commented Oct 13, 2021 at 10:52
  • @AndreiTătar But T should be what ever string is passed to the function. and hence res should be Task<T>. If not how do I achieve this ? Commented Oct 13, 2021 at 10:55
  • What type do you expect here Type here is not being infered ?? Commented Oct 13, 2021 at 11:20
  • @captain-yossarian since taskType is of type T . I expect res2 to be of the type Task<T> Commented Oct 13, 2021 at 11:21
  • @DollarAkshay res2 is distributed over the union Commented Oct 13, 2021 at 11:28

1 Answer 1

1

You need to make small refactor of types and make data one of the arguments.

export type TaskType = 'withdraw' | 'pickup'

interface Task<T extends TaskType = TaskType> {
  val: number
}

type Memory<Type extends TaskType> = {
  tasks: {
    [T in Type]: {
      [targetId: string]: Task<T>
    }
  }
}

const data: Memory<TaskType> = {
  tasks: {
    withdraw: {
      'a': { val: 0 }
    },
    pickup: {
      'a': { val: 0 }
    }
  }
}

const res1 = data.tasks.withdraw.a; // Type is set correctly to Task<"withdraw">


const fn = <
  Key extends TaskType,
  Data extends Memory<Key>
>(data: Data, taskType: Key) => {
  const res2 = data.tasks[taskType].a; // Task<Key>
};

Memory - should expect feneric Type

Playground

Sign up to request clarification or add additional context in comments.

1 Comment

Hey man I have updated the question to include a bit more info about my actual problem. Sadly while I appreciate the effort chaning Memory to a generic type simple does not work for me.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.