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?

7
  • The type of name is string. See what happens when you change let name to const name. Commented May 22, 2023 at 14:09
  • 1
    or let name: keyof typeof allthings = 'MyThing' Commented May 22, 2023 at 14:11
  • @zenly That's better than my answer to cast with as const Commented May 22, 2023 at 14:11
  • You don't want keyof typeof here because the value will be a union of all the property values. Just use const name = 'MyThing" Commented May 22, 2023 at 14:12
  • keyof typeof allthings is exactly equal to "MyThing" | "AnotherThing". "Find and use them dynamically based on a string" implies that the string is not a constant in the actual use case. Commented May 22, 2023 at 14:16

2 Answers 2

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]; 
}
Sign up to request clarification or add additional context in comments.

6 Comments

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
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 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.
1) Seems like your little tool is curried, so the doc comments should be changed to reflect that: tsplay.dev/WGdY9w 2) This tool also doesn't seem that useful anymore since satisfies exists now: tsplay.dev/WYQybW
@zenly I had never thought of combining Record with satisfies this way. Thanks, I'll update the post to use that...
|
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

}

6 Comments

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.
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';
@RuanMendes the objects are all the same shape hence they call of type CustomType.
@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.
@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.
|

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.