1

Perhaps what I'm asking to do is impossible, or I'm taking the wrong approach but is it possible to use a string to do a lookup on an imported namespace?

If I have a file: my_file.ts and its contents are many of these:

export const MyThing: CustomType = {
    propertyOne: "name",
    propertyTwo: 2
}
export const AnotherThing: CustomType = {
    propertyOne: "Another",
    propertyTwo: 3
}

Then I have a file that is going to import all these, but needs to find and use them dynamically based on a string:

import * as allthings from "dir/folder/my_file"


function doStuff() {
   let currentThing = allthings['MyThing']; // works
   let name = 'MyThing';
   let currentThing2 = allthings[name]; // doesnt work

}

The error I get here is:

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'typeof import("dir/folder/my_file")'. No index signature with a parameter of type 'string' was found on type 'typeof import("dir/folder/my_file")'.

Why does the literal string work but not a variable of type string?

Syed M. Sannan
  • 1,061
  • 2
  • 9
  • 28
user2616166
  • 139
  • 1
  • 10

2 Answers2

3

The literal string doesn't work because someone could reassign any string to your let name variable.

It will work if your string is const. Casting it is possible:

let name = 'MyThing' as const;

But you should just define a const variable which has the same effect. There's no reason to define a variable with let unless you intend to change it.

const name = 'MyThing';

See this TS playground example

If you don't know the key ahead of time, then if all the objects in my_file are of the same type (CustomType), then you can safely use keyof typeof allthings as the type

export const allthings = {
  MyThing: {
    propertyOne: "name",
    propertyTwo: 2
  },
  AnotherThing: {
    propertyOne: "Another",
    propertyTwo: 3
  }
};

type CustomType = typeof allthings[keyof typeof allthings];


function getValue(key: keyof typeof allthings): CustomType {
    return allthings[key]; 
}

See TS example

For these enum like objects, you can use satisfies Record<string, MyType> to enforce they are all of the same type

interface CustomType {
  propertyOne: string,
  propertyTwo: number
}

const allthings = {
  MyThing: {
    propertyOne: "name",
    propertyTwo: 2
  },
  AnotherThing: {
    propertyOne: "Another",
    propertyTwo: 3
  }
} satisfies Record<string, CustomType>;


function getValueFromAllThings(key: keyof typeof allthings): CustomType {
    return allthings[key]; 
}
Ruan Mendes
  • 90,375
  • 31
  • 153
  • 217
  • 1
    That's good, but you should indicate that the second form is preferable because it actually is constant meaning the type will in fact match the value in all cases – Aluan Haddad May 22 '23 at 14:13
  • Hey Ruan, thanks for the reply! Unless I'm missing something your TS playground is just a simple dict array within the same file. The issue I was having was when I specifically wanted to dynamically look for a variable within the imported "namespace" – user2616166 May 22 '23 at 14:30
  • @user2616166 The issue you were having is not what you specified in your question, your question says that you know exactly which string you're going to bracket access. It does not matter that they're in a separate file. If the exact string is not known by the compiler, it won't work, you will need to cast it manually to whatever key you want. – Ruan Mendes May 22 '23 at 14:37
  • 1) Seems like your little tool is curried, so the doc comments should be changed to reflect that: https://tsplay.dev/WGdY9w 2) This tool also doesn't seem that useful anymore since `satisfies` exists now: https://tsplay.dev/WYQybW – kelsny May 22 '23 at 14:57
  • @zenly I had never thought of combining `Record` with `satisfies` this way. Thanks, I'll update the post to use that... – Ruan Mendes May 22 '23 at 15:09
  • @user2616166 I wrote a function that given one of the keys in your `allthings`, it returns the corresponding object. `getValueFromAllThings('MyThing')` I am unsure of what you mean by dynamic. Your question still doesn't show your example where the string is dynamic. As I've explained, if the string is fully dynamic, you can't guarantee the shape of the object you're retrieving. – Ruan Mendes May 23 '23 at 14:04
0

I reread a similar question here: Element implicitly has an 'any' type because expression of type 'string' can't be used to index

And was finally able to get it to work by doing this:

import * as allthings from "dir/folder/my_file"


function doStuff() {
   let currentThing = allthings['MyThing']; // works
   let name = 'MyThing';
   let currentThing2 = allthings[name as keyof typeof allthings]; // does work

}
user2616166
  • 139
  • 1
  • 10
  • This only works if every sub-object is the same shape, if they are of different shapes, it will be an OR of all the shapes under `allthings`. – Ruan Mendes May 22 '23 at 14:36
  • 1
    Don't use a type assertion on a `string`-typed variable (`name`), but rather declare it with that type in the first place: `let name: keyof typeof allthings = 'MyThing';` – Bergi May 22 '23 at 14:47
  • @RuanMendes the objects are all the same shape hence they call of type CustomType. – user2616166 May 23 '23 at 09:36
  • @user2616166 What @Bergi suggested does work: `let name: keyof typeof allthings = 'MyThing'`. Also. I don't see anything dynamically accessing a key in an object??? Please do not mark this as the accepted answer since it doesn't show much. – Ruan Mendes May 23 '23 at 14:09
  • @user2616166 There is no guarantee from the compiler point of view that they are all of the same shape. My answer shows how to provide that guarantee. – Ruan Mendes May 23 '23 at 14:10
  • @user2616166 "*i cant say `var = 'MyThing'` as i dont know "mything" at this point, its dynamically finding the key in the object.*" - then please adjust the code in your question to show how you are dynamically finding the key. I assumed that you would simply replace the `'MyThing'` with a more dynamic expression, which doesn't change how `name` should be declared. – Bergi May 23 '23 at 18:59