A class with toString() can be used as a string, but how to convince the TypeScript compiler?

707 Views Asked by At

My class implements toString() to allow me to use any instance conveniently as a property name when building up other objects. The TypeScript compiler though is not convinced and outputs: a computed property name must be of type 'string' How can I declare my class in a way that satisfies the compiler?

class Foo {
  name: string  
  constructor(name: string) { this.name = name; }
  toString() { return this.name; }
}

which can be used thus:

const myFoo = new Foo('Clytemnestra');
const someObject = { [myFoo]: 'she murders Agamemnon'};

Currently the last line above produces the aforementioned type error.

1

There are 1 best solutions below

6
Inigo On

you can't

You can't "convince" the Typescript static type checker to ignore an object's type and rely on the coerced type -- that would defeat the whole purpose of static type checking.

but you could...

0verride the statically determined type by changing the offending line thusly:

const someObject = { [myFoo as unknown as string ]: 'she murders Agamemnon'};

That smells bad immediately.

Ok, how about:

const someObject = { [ myFoo.toString() ]: 'she murders Agamemnon'};

This is slightly better, as you are explicitly doing the "coercing" to string.

But both of these ultimately are a bad idea. Any object can be coerced into string, the default value often being "[object Object]". Relying on the toString() method undermines type safety. So you're again defeating the purpose of static type checking. You must ask: why am I using Typescript?

instead, a more type safe approach

class Subject {
  name: string
  city: string
  constructor(name: string, city: string) { 
    this.name = name
    this.city = city
  }
}

const vengefulWife = new Subject('Clytemnestra', 'Mycenae');

const summaries = new Map<Subject, string>()
summaries.set(vengefulWife, 'she murders Agamemnon')


for (const [key, value] of summaries) {
  console.log(`${key.name} of ${key.city}: ${value}`);
}

Because Typescript knows the type of key, it recognizes the references to key.name and key.city as valid