'Typescript: No index signature with a parameter of type 'string' was found on type '{ "A": string; }

I have some vanilla javascript code that takes a string input, splits the string into characters, and then matches those characters to a key on an object.

DNATranscriber = {
    "G":"C",
    "C": "G",
    "T": "A",
    "A": "U"
}
function toRna(sequence){
    const sequenceArray = [...sequence];
    const transcriptionArray = sequenceArray.map(character =>{
        return this.DNATranscriber[character];
    });

    return transcriptionArray.join("");
}

console.log(toRna("ACGTGGTCTTAA")); //Returns UGCACCAGAAUU

This works as expected. I'd now like to convert this to typescript.

class Transcriptor {
    DNATranscriber = {
       G:"C",
       C: "G",
       T: "A",
       A: "U"
    }
    toRna(sequence: string) {
        const sequenceArray = [...sequence];
        const transcriptionArray = sequenceArray.map(character =>{
            return this.DNATranscriber[character];
        });
    }
}

export default Transcriptor

But I'm getting the following error.

Element implicitly has an 'any' type because expression of type 'string' >can't be used to index type '{ "A": string; }'. No index signature with a parameter of type 'string' was found on type >'{ "A": string; }'.ts(7053)

I thought that the issue was that I needed my object key to be a string. But converting them to strings didn't work.

DNATranscriber = {
       "G":"C",
       "C": "G",
       "T": "A",
       "A": "U"
    }

I'm quite confused by this. It says that no index signature with a type of string exists on my object. But I'm sure that it does. What am I doing wrong?

Edit - I solved this by giving the DNATranscriber object a type of any.

DNATranscriber: any = {
    "G":"C",
    "C":"G",
    "T":"A",
    "A":"U"
}


Solution 1:[1]

You can fix the errors by validating your input, which is something you should do regardless of course.

The following typechecks correctly, via type guarding validations

const DNATranscriber = {
    G: 'C',
    C: 'G',
    T: 'A',
    A: 'U'
};

export default class Transcriptor {
    toRna(dna: string) {
        const codons = [...dna];
        if (!isValidSequence(codons)) {
            throw Error('invalid sequence');
        }
        const transcribedRNA = codons.map(codon => DNATranscriber[codon]);
        return transcribedRNA;
    }
}

function isValidSequence(values: string[]): values is Array<keyof typeof DNATranscriber> {
    return values.every(isValidCodon);
}
function isValidCodon(value: string): value is keyof typeof DNATranscriber {
    return value in DNATranscriber;
}

It is worth mentioning that you seem to be under the misapprehention that converting JavaScript to TypeScript involves using classes.

In the following, more idiomatic version, we leverage TypeScript to improve clarity and gain stronger typing of base pair mappings without changing the implementation. We use a function, just like the original, because it makes sense. This is important! Converting JavaScript to TypeScript has nothing to do with classes, it has to do with static types.

const DNATranscriber = {
    G: 'C',
    C: 'G',
    T: 'A',
    A: 'U'
};

export default function toRna(dna: string) {
    const codons = [...dna];
    if (!isValidSequence(codons)) {
        throw Error('invalid sequence');
    }
    const transcribedRNA = codons.map(codon => DNATranscriber[codon]);
    return transcribedRNA;
}

function isValidSequence(values: string[]): values is Array<keyof typeof DNATranscriber> {
    return values.every(isValidCodon);
}
function isValidCodon(value: string): value is keyof typeof DNATranscriber {
    return value in DNATranscriber;
}

Update:

Since TypeScript 3.7, we can write this more expressively, formalizing the correspondence between input validation and its type implication using assertion signatures.

const DNATranscriber = {
    G: 'C',
    C: 'G',
    T: 'A',
    A: 'U'
} as const;

type DNACodon = keyof typeof DNATranscriber;
type RNACodon = typeof DNATranscriber[DNACodon];

export default function toRna(dna: string): RNACodon[] {
    const codons = [...dna];
    validateSequence(codons);
    const transcribedRNA = codons.map(codon => DNATranscriber[codon]);
    return transcribedRNA;
}

function validateSequence(values: string[]): asserts values is DNACodon[] {
    if (!values.every(isValidCodon)) {
        throw Error('invalid sequence');    
    }
}
function isValidCodon(value: string): value is DNACodon {
    return value in DNATranscriber;
}

You can read more about assertion signatures in the TypeScript 3.7 release notes.

Solution 2:[2]

Also, you can do this:

(this.DNATranscriber as any)[character];

Edit.

It's HIGHLY recommended that you cast the object with the proper type instead of any. Casting an object as any only help you to avoid type errors when compiling typescript but it doesn't help you to keep your code type-safe.

E.g.

interface DNA {
    G: "C",
    C: "G",
    T: "A",
    A: "U"
}

And then you cast it like this:

(this.DNATranscriber as DNA)[character];

Solution 3:[3]

This was what I did to solve my related problem

interface Map {
  [key: string]: string | undefined
}

const HUMAN_MAP: Map = {
  draft: "Draft",
}

export const human = (str: string) => HUMAN_MAP[str] || str

Solution 4:[4]

You have two options with simple and idiomatic Typescript:

  1. Use index type
DNATranscriber: { [char: string]: string } = {
  G: "C",
  C: "G",
  T: "A",
  A: "U",
};

This is the index signature the error message is talking about. Reference

  1. Type each property:
DNATranscriber: { G: string; C: string; T: string; A: string } = {
  G: "C",
  C: "G",
  T: "A",
  A: "U",
};

Solution 5:[5]

Don't Use Any, Use Generics

// bad
const _getKeyValue = (key: string) => (obj: object) => obj[key];
    
// better
const _getKeyValue_ = (key: string) => (obj: Record<string, any>) => obj[key];
    
// best
const getKeyValue = <T extends object, U extends keyof T>(key: U) => (obj: T) =>
      obj[key];

Bad - the reason for the error is the object type is just an empty object by default. Therefore it isn't possible to use a string type to index {}.

Better - the reason the error disappears is because now we are telling the compiler the obj argument will be a collection of string/value (string/any) pairs. However, we are using the any type, so we can do better.

Best - T extends empty object. U extends the keys of T. Therefore U will always exist on T, therefore it can be used as a look up value.

Here is a full example:

I have switched the order of the generics (U extends keyof T now comes before T extends object) to highlight that order of generics is not important and you should select an order that makes the most sense for your function.

const getKeyValue = <U extends keyof T, T extends object>(key: U) => (obj: T) =>
  obj[key];

interface User {
  name: string;
  age: number;
}

const user: User = {
  name: "John Smith",
  age: 20
};

const getUserName = getKeyValue<keyof User, User>("name")(user);

// => 'John Smith'

Alternative syntax

const getKeyValue = <T, K extends keyof T>(obj: T, key: K): T[K] => obj[key];

Solution 6:[6]

This will eliminate the error and is type safe:

this.DNATranscriber[character as keyof typeof DNATranscriber]

Solution 7:[7]

On your params you have to define the keyOf Object.

interface User {
    name: string
    age: number 
}

const user: User = {
    name: "someone",
    age: 20
}

function getValue(key: keyof User) {
    return user[key]
}

Solution 8:[8]

Solved similar issue by doing this:

export interface IItem extends Record<string, any> {
    itemId: string;
    price: number;
}

const item: IItem = { itemId: 'someId', price: 200 };
const fieldId = 'someid';

// gives you no errors and proper typing
item[fieldId]

Solution 9:[9]

I resolved a similar issue in my getClass function like this:

import { ApiGateway } from './api-gateway.class';
import { AppSync } from './app-sync.class';
import { Cognito } from './cognito.class';

export type stackInstances = typeof ApiGateway | typeof  AppSync | typeof Cognito

export const classes = {
  ApiGateway,
  AppSync,
  Cognito
} as {
  [key: string]: stackInstances
};

export function getClass(name: string) {
  return classes[name];
}

Typing my classes const with my union type made typescript happy and it makes sense to me.

Solution 10:[10]

For those who Google:

No index signature with a parameter of type 'string' was found on type...

most likely your error should read like:

Did you mean to use a more specific type such as keyof Number instead of string?

I solved a similar typing issue with code like this:

const stringBasedKey = `SomeCustomString${someVar}` as keyof typeof YourTypeHere;

This issue helped me to learn the real meaning of the error.

Solution 11:[11]

I believe this one might serve you better.

With this you'll get suggestions while typing your arguments (try it in an editor), and a strong return type for later use.

Screenshot of suggestions

Also, inspired by Aluan Haddad's answer, you get sequence validation, but a bit more efficiently, as validation is made inside of the transcription loop.

type DNAletter = 'G' | 'C' | 'T' | 'A';
type RNAletter = 'C' | 'G' | 'A' | 'U';

const DNATranscriber: { [key in DNAletter]: RNAletter } = {
  G: 'C',
  C: 'G',
  T: 'A',
  A: 'U'
};

// Return `RNAletter[]`
function toRna(sequence: string | string[] | DNAletter[]) {
  return ([...sequence] as DNAletter[]).map(character => {
    const transcribed = DNATranscriber[character];
    if (transcribed === undefined)
      throw Error(`Invalid character "${character}" in sequence`);
    return transcribed;
  });
}

EDIT: As of TS3.4 you can use as const

Solution 12:[12]

You can use Record, for example.

let DNATranscriber: Record<string, string> = {};

Solution 13:[13]

I messed around with this for awhile. Here was my scenario:

I have two types, metrics1 and metrics2, each with different properties:

type metrics1 = {
    a: number;
    b: number;
    c: number;
}

type metrics2 = {
    d: number;
    e: number;
    f: number;
}

At a point in my code, I created an object that is the intersection of these two types because this object will hold all of their properties:

const myMetrics: metrics1 & metrics2 = {
    a: 10,
    b: 20,
    c: 30,
    d: 40,
    e: 50,
    f: 60
};

Now, I need to dynamically reference the properties of that object. This is where we run into index signature errors. Part of the issue can be broken down based on compile-time checking and runtime checking. If I reference the object using a const, I will not see that error because TypeScript can check if the property exists during compile time:

const myKey = 'a';
console.log(myMetrics[myKey]); // No issues, TypeScript has validated it exists

If, however, I am using a dynamic variable (e.g. let), then TypeScript will not be able to check if the property exists during compile time, and will require additional help during runtime. That is where the following typeguard comes in:

function isValidMetric(prop: string, obj: metrics1 & metrics2): prop is keyof (metrics1 & metrics2) {
    return prop in obj;
}

This reads as,"If the obj has the property prop then let TypeScript know that prop exists in the intersection of metrics1 & metrics2." Note: make sure you surround metrics1 & metrics2 in parentheses after keyof as shown above, or else you will end up with an intersection between the keys of metrics1 and the type of metrics2 (not its keys).

Now, I can use the typeguard and safely access my object during runtime:

let myKey:string = '';
myKey = 'a';
if (isValidMetric(myKey, myMetrics)) {
    console.log(myMetrics[myKey]);
}

Solution 14:[14]

My solution is

type DNATranscriber = {
   G: string,
   C: string,
   T: string,
   A: string,
}
type DNATanscriberIndex = {
   [key: string]: string
}

let variableName:DNATanscriberIndex&DNATanscriber

The DNATranscriber type is for Typescript to be able to reference the fields and DNATanscriberIndex type is to declare the index as a string

Solution 15:[15]

Here is the function example trim generic type of array object

const trimArrayObject = <T>(items: T[]) => {

  items.forEach(function (o) {

    for (let [key, value] of Object.entries(o)) {

      const keyName = <keyof typeof o>key;

      if (Array.isArray(value)) {

        trimArrayObject(value);

      } else if (typeof o[keyName] === "string") {

        o[keyName] = value.trim();

      }

    }

  });

};

Solution 16:[16]

Here is a solution to this problem without using object keys:

function toRna(value: string): string {
  return value.split('').map(ch => 'CGAU'['GCTA'.indexOf(ch)]).join('');
}

console.log(toRna('ACGTGGTCTTAA')); 
\\UGCACCAGAAUU

Solution 17:[17]

you may use type a return type to get, just like this.

getAllProperties(SellRent: number) : Observable<IProperty[]>{
return this.http.get<IProperty[]>('data/properties.json').pipe(
  map(data => {

    const propertiesArray: Array<IProperty> = [];
    for(const id in data){
      if(data.hasOwnProperty(id) && data[id].SellRent === SellRent){
        propertiesArray.push(data[id]);
      }
    }
    return propertiesArray;
  })
)

}

Solution 18:[18]

I know this is an old question but TS provides an easier way to type your problem now than it did when asked... As of TS3.4, the simplest approach here nowadays would be to use "as const" Typing an object as any is never the right solution IMO

DNATranscriber = {
    "G":"C",
    "C": "G",
    "T": "A",
    "A": "U"
} as const;

Means that ts now knows these keys and values will not change and therefore can be assessed by infer. This means that TS already know that DNATranscriber["G"] will be "C" and can do checks on output code also which is far more helpful.

Previously... As in Marias's response

type Keys = "G" | "C" | "T" | "A";
type values "C" | "G" | "A" | "U";
DNATranscriber: {[K in Keys]: values} = {
    "G":"C",
    "C": "G",
    "T": "A",
    "A": "U"
};

Not ideal as it did not represent the static nature of the mapping.

Solution 19:[19]

A quick workaround will be like bellow, or at least how I am doing it:

(obj as { [k in string]: any })[key]

Solution 20:[20]

For anyone struggling with similar cases

No index signature with a parameter of type 'string' was found on type X

trying to use it with simple objects (used as dicts) like:

DNATranscriber = {
   G:"C",
   C: "G",
   T: "A",
   A: "U"
}

and trying to dynamically access the value from a calculated key like:

const key = getFirstType(dnaChain);
const result = DNATranscriber[key];

and you faced the error as shown above, you can use the keyof operator and try something like

const key = getFirstType(dnaChain) as keyof typeof DNATranscriber;

certainly you will need a guard at the result but if it seems more intuitive than some custom types magic, it is ok.