My code looks like this. I define an interface Entity that has the method attributes and then variable keys that are created by that method.
interface Entity {
id: number
attributes(): { [attribute: string]: any }
[attribute: string]: any
}
class BasicEntity implements Entity {
id: number
constructor(id: number) {
this.id = id
for (const [name, value] of Object.entries(this.attributes())) {
this[name] = value
}
}
attributes() {
return {
DATE: 'TODAY',
OPINION: 'BASED',
}
}
}
const entity = new BasicEntity(0)
entity['DATE'] = 'TOMORROW' // Does compile, no type hinting
entity.DATE = 'TOMORROW' // Doesn't compile
I get no type hints from typescript when I access them using indexing, and accessing them with dot syntax is impossible, because typescript doesn't figure out the types at compile time.
Is there any way I could retain typescript type hinting while populating the properties at runtime (when I obviously know the this.attributes()
return value, as it's constant and known at compile time).
CodePudding user response:
If I understand correctly, the this.attributes()
method is supposed to define the shape of the Entity?
In that case, we should use a generic to explain this association to TypeScript:
type Entity<T> = {
id: number
attributes(): T
} & T
Unfortunately, if there is no hint of the generic in the constructor call, TypeScript cannot automatically infer it, so it now becomes much more verbose since we have to explicitly specify the concrete type, instead of relying lazily on the return type of this.attributes()
:
class BasicEntity implements Entity<{
DATE: string
OPINION: string
}> {
id: number
DATE
OPINION
constructor(id: number) {
this.id = id
// Object.entries is loosely typed
// for (const [name, value] of Object.entries(this.attributes())) {
// this[name] = value
// }
const data = this.attributes()
this.DATE = data.DATE
this.OPINION = data.OPINION
}
attributes() {
return {
DATE: 'TODAY',
OPINION: 'BASED',
}
}
}
const entity = new BasicEntity(0)
entity['DATE'] = 'TOMORROW' // Okay
entity['DATE'] = false // ERROR: Type 'boolean' is not assignable to type 'string'.
entity.DATE = 'TOMORROW' // Okay
entity.DATE = false // ERROR: Type 'boolean' is not assignable to type 'string'.