I'm trying to introduce a lookup type within an object. Let's say my object looks like
class PersonList {
persons = {
john: 'description of john',
bob: 'description of bob'
}
}
I'd like to have a getter to get a person from persons
but without specifying the the persons
object.
The getProperty
from the docs
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key]; // Inferred type is T[K]
}
Wants to have an obj
which I want to get rid of in my getter. I've tried proxying the getter, but that didn't work out:
class PersonList {
persons = {
john: 'description of john',
bob: 'description of bob'
};
getPerson(name) {
return this.getProperty(this.persons, name);
}
private getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key]; // Inferred type is T[K]
}
}
This sadly doesn't throw an error when trying to do something like personList.getPerson('someonenotincluded');
- and additionally autocomplete doesn't work either.
I'd take that inline type and name it (but keep reading, you don't have to):
interface Persons {
john: string;
bob: string;
}
Then you can use keyof Persons
as the parameter type in getPerson
:
class PersonList {
persons: Persons = {
john: 'description of john',
bob: 'description of bob'
};
getPerson(name: keyof Persons) {
return this.persons[name];
}
}
So then if pl
is a PersonList
:
console.log(pl.getPerson('john')); // Works
console.log(pl.getPerson('someonenotincluded')); // Error
Live on the playground.
But, if you prefer to keep it inline, you can by using keyof PersonList['persons']
as the parameter type:
class PersonList {
persons = {
john: 'description of john',
bob: 'description of bob'
};
getPerson(name: keyof PersonList['persons']) {
return this.persons[name];
}
}
Live on the playground.
In a comment you've asked:
is it possible to implement this in an abstract class? ... it would be awesome to implement the getter in the abstract class, but I haven't found a solution so far.
...with a link to this code template:
abstract class AbstractPersonList {
protected abstract persons: { [name: string]: string };
}
class Persons extends AbstractPersonList {
persons = {
john: 'this is john',
}
}
class MorePersons extends AbstractPersonList {
persons = {
bob: 'this is bob',
}
}
You can parameterize AbstractPersonList
:
abstract class AbstractPersonList<T extends {[name: string]: string}> {
// ------------------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
protected abstract persons: T;
public getPerson(name: keyof T): string {
return this.persons[name];
}
}
Then you'd have:
class Persons extends AbstractPersonList<{john: string}> {
// -------------------------------------^^^^^^^^^^^^^^^^
persons = {
john: 'this is john',
}
}
class MorePersons extends AbstractPersonList<{bob: string}> {
// -----------------------------------------^^^^^^^^^^^^^^^
persons = {
bob: 'this is bob',
}
}
Which leads to these results, which I think are what you're looking for:
let n = Math.random() < 0.5 ? 'john' : 'bob';
const p = new Persons();
console.log(p.getPerson('john')); // Works
console.log(p.getPerson('bob')); // FAILS: Argument of type '"bob"' is not assignable to parameter of type '"john"'.
console.log(p.getPerson(n)); // FAILS: Argument of type 'string' is not assignable to parameter of type '"john"'.
const mp = new MorePersons();
console.log(mp.getPerson('john')); // FAILS: Argument of type '"john"' is not assignable to parameter of type '"bob"'.
console.log(mp.getPerson('bob')); // Works
console.log(mp.getPerson(n)); // FAILS: Argument of type 'string' is not assignable to parameter of type '"bob"'.
Live on the playground.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With