0

I'm trying to type an interface (or type) with the following logic:

interface MyArgs<Name, Id> {
  items: {
    [Name]: string,
    [Id]: string
  }[];
  idKey: string;
  nameKey: string;
}

In more detail:

I'm getting an array of items of an unknown type/interface (it's dynamic, they can be anything). While I could just type them as any[], I'd like to know if it's possible to be a bit more specific about their type, and pass Typescript the information that I do have:

each item will be having an id property (e.g: guid, id, uuid) and a name property (e.g: name, label, title). The exact idKey and nameKey are provided as arguments (idKey, nameKey).

I want to declare the values provided in idKey and nameKey as valid keys for items records (e.g: { [idKey]: string; [nameKey]: string }(

What I tried above doesn't work. I also tried this:

interface MyArgs<Name, Id> {
  items: { <Name>: string; <Id> : string }[]
  idKey: string
  nameKey: string
}

which is, too, invalid.

How can I achieve this?

Note: if the above would be possible without having to type the values twice (once inside <> and once in the values of idKey and nameKey, that would be even more awesome).

5
  • If the keys are as you said - dynamic, then how do you expect Typescript to know them during compile time? Commented Oct 6, 2022 at 13:28
  • @Mke, I want typescript to treat them as variables. It shouldn't care about their actual value, but it should know that item[idKey] is a string and so is item[nameKey], without me having to declare as string. Commented Oct 6, 2022 at 13:29
  • Does this give you any pointers? Commented Oct 6, 2022 at 13:30
  • @caTS, probably, although I don't fully understand it, I'll play with it. Thanks! Commented Oct 6, 2022 at 13:32
  • @caTS, It's clear to me that infer is the right way to do it, but the implementation details for my specific case elude me (I keep getting errors). I'd appreciate an answer if you happen to know how to do it. Thanks! Commented Oct 6, 2022 at 14:26

1 Answer 1

1

Intersect mapped types (or rather, Records) to create the correct object type:

interface MyArgs<Name extends string, Id extends string> {
  items: (
    { [_ in Name]: string; } &
    { [_ in Id]: string; }
  )[];
  idKey: string;
  nameKey: string;
}

If you need to, you can also constrain Name and Id to more specific keys:

interface MyArgs<Name extends "name" | "label" | "title", Id extends "guid" | "id" | "uuid"> {

You could also use Records to simplify it further:

  items: (
    Record<Name, string> & Record<Id, string>
  )[];

Or even just:

  items: Record<Id | Name, string>[];
Sign up to request clarification or add additional context in comments.

8 Comments

With both syntaxes above I'm getting this error. I'm using "typescript": "~4.4.0" (installed: 4.4.4). Do I need a higher version?
Ah no, you just need to add a generic constraint. Probably Name extends string and Id extends string.
Awesome, works like a charm. But I don't really understand how I could have reached this result using the question you pinpointed as similar.
I used mapped types to create dynamically named keys. The focus in the linked question was this and not the usage of infer. Sorry if it was a little misleading :)
Sure, I did not realize I could do Record<Id | Name, string> :)
|

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.