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"
}
toRna
any
and it'll fix it, the same way that taking the battery out of a smoke detector fixes a potential fire.
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];
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.
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?
keyof typeof
was helpful!
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
You have two options with simple and idiomatic Typescript:
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
Type each property:
DNATranscriber: { G: string; C: string; T: string; A: string } = {
G: "C",
C: "G",
T: "A",
A: "U",
};
keyof
or Index Type Query concept is really confusing. I wish someone could explain it in plain English without the use of cryptic letters like T
or K
. Use a real example.
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];
const getUserName = getKeyValue<User, keyof User>(user, "name");
This will eliminate the error and is type safe:
this.DNATranscriber[character as keyof typeof DNATranscriber]
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.
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]
}
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]
IItem
with the Record<string, any>
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.
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.
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.
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.
https://i.stack.imgur.com/iULAa.png
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
You can use Record, for example.
let DNATranscriber: Record<string, string> = {};
let DNATranscriber: { [key: string]: string } = {};
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]);
}
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
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();
}
}
});
};
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
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;
})
)
}
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.
A quick workaround will be like bellow, or at least how I am doing it:
(obj as { [k in string]: any })[key]
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
}
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.
Success story sharing
DNA
type isn't a bad idea but wouldn'tthis.DNATranscriber
then be declared likeDNATranscriber: DNA
making the "cast" redundant?