298

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"
}
onTheInternet
  • 6,421
  • 10
  • 41
  • 74
  • it's not the answer, but you forgot to return the value from `toRna` – Reza Jun 12 '19 at 18:39
  • What is your typescript version? I don't get any error https://stackblitz.com/edit/angular-kupcve` – Reza Jun 12 '19 at 18:45
  • It's version "2.5.3" – onTheInternet Jun 12 '19 at 18:47
  • I tried with "3.1.1", may be it's time to upgrade ;) – Reza Jun 12 '19 at 18:49
  • I downgrade my stackblitz to 2.5.3 and still no error – Reza Jun 12 '19 at 18:51
  • I'm actually doing this as part of a training on https://exercism.io. I'm not sure it's possible to update the typescript version. – onTheInternet Jun 12 '19 at 18:51
  • Weird. I have no idea why this isn't working then. – onTheInternet Jun 12 '19 at 18:59
  • Also there was a weird issue, in es5 if you spread a string like [...string] it will return the char array. in my stackblitz it wasn't doing that, so I had to use Array.from(string) to make it work. May be try that as well (copy paste the code from my stackblitz) – Reza Jun 12 '19 at 19:02
  • I tried that. Still getting the same issue. – onTheInternet Jun 12 '19 at 19:04
  • What do you expect to happen when you call `toRNA("OBVIOUS NONSENSE")`? – jcalz Jun 12 '19 at 19:09
  • I fixed it. I'll add an update. – onTheInternet Jun 12 '19 at 19:09
  • 283
    Sure, type something as `any` and it'll fix it, the same way that taking the battery out of a smoke detector fixes a potential fire. – jcalz Jun 12 '19 at 19:12
  • 8
    Your metaphor is a little clumsy but I still think you make a very valid point. I'll think on this and try to come up with a better solution. – onTheInternet Jun 12 '19 at 19:15
  • 8
    Ouch, you hit me right in the metaphor. Anyway, [this](https://typescript-play.js.org/#code/MYewdgzgLgBAIgOQIIBUBOBDSw0EsBGApmjALwwDeAUDDAOIBcMARAMLMA0NMrTzdnbij5JBtJHwCqzKgF8A3FSoAzAK5hgUXOBhQQAJTAYAFBEIBHVYQ2Em0PGADmASkrdQkWLggA1DABtcABMEVWB-QhB8DDMyGGMADzsoBxcmBJhvGABrQgBPEGVdPIAHSKLEVExsPCISUgA+bloM3DB4ZHQsCBwCYkVaD2gYM0trYEIkNEw8uIBtADol0asbAF0BmCHYFO7ekq1wKZm4lfHJ6Yw85pgF5Vx-KGJjbz9AkLCIqJjCZxuFgC2GBKxmAAAsMJhNMQyA03LQETA0IQoKo0O1Kl0an00HNwZCMNC0BsbrJnIpuMjUejdNUengDtowMcrgsAFYgNrGZjMclyJRDEARBb+ECOYx6QwmZhIVh0FB0eWsFAoJCiZzkmAAei1+hRaMgMEkdFYstNdDVkkkVCAA) is how I'd do it – jcalz Jun 12 '19 at 19:16
  • Thank you @jcalz. I was thinking about how I could incorporate checks for invalid input. I really appreciate your time. – onTheInternet Jun 12 '19 at 19:17
  • Thanks putting this in comments TS7053 so hopefully anyone with this issue gets a good answer TS7053 – Daniel Tate Oct 26 '21 at 05:03

24 Answers24

220

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];
Christos Lytras
  • 36,310
  • 4
  • 80
  • 113
  • 6
    Hey I just did what you said in your edit version. but still got error – Archsx Jun 26 '20 at 07:57
  • 8
    An explicit `DNA` type isn't a bad idea but wouldn't `this.DNATranscriber` then be declared like `DNATranscriber: DNA` making the "cast" redundant? – Aluan Haddad Sep 24 '20 at 02:01
124

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

Manoel Quirino Neto
  • 1,261
  • 1
  • 9
  • 2
117

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.

Jeremy Moritz
  • 13,864
  • 7
  • 39
  • 43
Aluan Haddad
  • 29,886
  • 8
  • 72
  • 84
  • 2
    As an alternative, is it possible to add an index signature to `DNATranscriber`? Since the error says `"Typescript: No index signature with a parameter of type 'string' was found on type '{ “A”: string; }"`, it implies that there is a way to add an index signature of type 'string'. Can this be done? – Cameron Hudson Dec 10 '19 at 05:25
  • 1
    Yes you could do that, but then the code wouldn't be type safe or expressive in the way that the question intended. There is a reason he didn't write it that way, a good reason. – Aluan Haddad Dec 10 '19 at 20:39
42

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",
};
38

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]
}
Tyler2P
  • 2,324
  • 26
  • 22
  • 31
24

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];
Alex Mckay
  • 3,463
  • 16
  • 27
  • 1
    How would this work if User had another key with an interface as is type? I get an error that they cannot be assigned to 'string'. – lockykeaney Jun 04 '20 at 04:16
  • There you go: https://codesandbox.io/s/recursing-sun-cundn?file=/src/index.ts – Alex Mckay Jun 04 '20 at 21:12
  • I wrote a tiny [npm package](https://www.npmjs.com/package/get-key-value) with this function to make this task easier for those that are new to Typescript. It is 38 bytes once minified and contains a jsdoc comment so if you hover over the function it provides the answer above. – Alex Mckay Jul 20 '20 at 01:33
  • When using the suggested **Alternate syntax** for getKeyValue I needed to modify the getUserName line to something like this: `const getUserName = getKeyValue(user, "name");` – bammmmmmmmm Mar 18 '21 at 14:01
23

This will eliminate the error and is type safe:

this.DNATranscriber[character as keyof typeof DNATranscriber]
Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
Almeno Soares
  • 231
  • 2
  • 2
  • 4
    This is not type safe. If `character` has a dynamic value that cannot be known at compile time, then it cannot be guaranteed that it will always be of type `keyof typeof DNATranscriber`. Using the type assertion with `as` here is introducing a type safety bug. – Jimmy May 15 '21 at 23:57
19

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]
Eugene Kriulin
  • 251
  • 3
  • 3
  • 2
    Can you add an explanation as to what this block of code does? – Rastalamm Dec 08 '20 at 21:39
  • Sure! By extending `IItem` with the `Record` you allow an object to contain other `string` keys with `any` values along with those defined in the interface. The nice part is that you still have the autocompletion for the defined properties. – Eugene Kriulin Jan 28 '21 at 23:52
  • This should be the correct answer. It does not need explanation, without really understand what is under the hood, I can grasp we are saying that IItem is a Record with keys string, value any. This worked, and it is a clean solution. – Nestoter Jul 29 '22 at 13:09
  • This "extends Record" is a life saver thank you so much – Ofer Gal Jan 07 '23 at 08:57
10

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.

Mattijs
  • 3,265
  • 3
  • 38
  • 35
8

You can use Record, for example.

let DNATranscriber: Record<string, string> = {};
Galterius
  • 109
  • 1
  • 3
7
const Translator : { [key: string]: string } = {
  G: "C",
  C: "G",
  T: "A",
  A: "U"
 }
 
 export function toRna(DNA:string) {

   const Translate = [...DNA];
   let Values = Translate.map((dna) => Translator[dna])
   if (Validate(Values)) {return Values.join('')}
 }

export function Validate(Values:string[]) : Boolean{
 if (Values.join('') === "" || Values.join('').length !== Values.length) throw Error('Invalid input DNA.');
 return true
}
Lucasc12
  • 71
  • 1
  • 1
  • 2
    Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jun 22 '22 at 06:37
  • Thanks for this: `{[key: string]: string}` This helped me understand what is really going on and how to define these Object Literals as typed objects in the TypeScript world. I believe these types are defined in TypeScript by static interfaces at compile time as that is an Interface. Using `{[key: string]: string}` you are also saying that the object is an "associative array" inside an object whose keys are strings and associated objects are also strings. TypeScript really needs this type of object literal definition now to truly pass their strict type tests. So I appreciate your answer. – Stokely Jun 07 '23 at 12:38
4

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.

Pavot
  • 870
  • 10
  • 8
3

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

Marius Brataas
  • 614
  • 1
  • 5
  • 8
3

I used {[x:string]:string} in the Object.

const myObj:{[x:string]:string} = {
  'a': 'b'
};

function myFn() {
  const getVal = myObj['a'];
};
shahul01
  • 113
  • 2
  • 7
1

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]);
}
Josh Weston
  • 1,632
  • 22
  • 23
1

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

MyPoint
  • 21
  • 3
0

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();

      }

    }

  });

};
0

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
Matthias
  • 1,150
  • 20
  • 38
0

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;
  })
)

}

Mahmmoud Kinawy
  • 551
  • 4
  • 11
0

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.

0

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

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

Without having to add type narrowing functions to validate the stream sequence:

type DNATranscriber = {
    G: string;
    C: string;
    T: string;
    A: string;
}

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

export default Transcriptor

Note this will change your contract on the function putting the onus on the caller to pass an array of valid characters rather than a string.

Unfortunately I don't know of a way to define a type literal of any possible combination of substrings so you can still use a string.

Juan Valencia
  • 38
  • 1
  • 5
-1

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.

Dharman
  • 30,962
  • 25
  • 85
  • 135
Igneel64
  • 618
  • 4
  • 10
-1

These are added to tsconfig.json.

compilerOptions:{

  "suppressImplicitAnyIndexErrors": true,
  "strictNullChecks":false,
  "strictPropertyInitialization": false,

 }
  • 1
    As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Mar 15 '23 at 06:40
  • 1
    ignoring errors doesn't fix errors actually – Nickensoul Jul 24 '23 at 09:21