Reputation: 139
I'm looking to create a class that will have unknown properties. Basically, I pass an object to its constructor and it uses the properties of that object. This is easy in JavaScript but seemingly a pain in TypeScript.
I'm looking to add several methods to specific objects, not all - or shorthand something like the following (just an example):
function toString() {
return JSON.stringify(this).replace(
/[\u007f-\uffff]/g,
(character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, '0')}`,
);
}
const obj = { pencil: '✎' };
Object.definePropery(
obj,
'toString',
{ value: toString.bind(obj) }
);
console.log(`${obj}`); // {"pencil":"\u270e"}
This is just an example, but how would I create a class that could simply (just an example):
class MyClass {
constructor(/*...*/) {
// ...
}
toString() {
return JSON.stringify(this).replace(
/[\u007f-\uffff]/g,
(character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, '0')}`,
);
}
anotherMethod() {
// ...
}
yetAnotherOne() {
// ...
}
}
const myClass1 = new MyClass({ hello: 'world' });
console.log(myClass1.hello); // world
console.log(`JSON: ${myClass1}`); // {"hello":"world"}
console.log(myClass1.anotherMethod());
const myClass2 = new MyClass({ foo: 'bar' });
console.log(myClass2.foo); // bar
console.log(myClass2.yetAnotherOne());
Maybe I'm overcomplicating things, but any help is appreciated.
Thanks!
Upvotes: 0
Views: 790
Reputation: 2916
I found a way to minimize the amount of casting "as any" (by taking advantage of how you can narrow down the type of a class using an interface):
interface MyClass {
[key: string]: any;
}
class MyClass {
constructor(obj: any) {
for (const prop in obj) {
this[prop] = obj[prop];
}
}
toString() {
return JSON.stringify(this).replace(
/[\u007f-\uffff]/g,
(character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, '0')}`,
);
}
anotherMethod() {
// ...
}
yetAnotherOne() {
// ...
}
}
const myClass1 = new MyClass({ hello: 'world' });
console.log(myClass1.hello); // world
console.log(`JSON: ${myClass1}`); // {"hello":"world"}
console.log(myClass1.anotherMethod());
const myClass2 = new MyClass({ foo: 'bar' });
console.log(myClass2.foo); // bar
console.log(myClass2.yetAnotherOne());
It may be improved if you know exactly the type of the values, for example
interface MyClass {
[key: string]: string | ((...args: any[]) => any);
}
The ((...args: any[]) => any)
part is used to match the functions like anotherMethod()
Edit:
Found a simpler way, you can add [key: string]: any
directly within the class definition:
class MyClass {
[key: string]: any;
constructor(obj: any) {
for (const prop in obj) {
this[prop] = obj[prop];
}
}
toString() {
return JSON.stringify(this).replace(
/[\u007f-\uffff]/g,
(character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, '0')}`,
);
}
anotherMethod() {
// ...
}
yetAnotherOne() {
// ...
}
}
Upvotes: 1
Reputation: 31
From what I gather you would be better just using objects and adding properties as you normally would:
const obj = {pencil: '✎'}
if(condition) obj['lock'] = 🔒
You could otherwise do what isaactfa says, but you would be definitively overcomplicating things
Upvotes: 1
Reputation: 6651
Anything you can do in JS you can also do in TS, so this is possible. It does, however, go completely against everything TypeScript is meant to be good for, so it's a little annoying to write. You'll just have to cast any MyClass
object to a type with the appropriate fields whenever you want to access them. Most conveniently, any
. Otherwise, you'll have to go through unknown
first:
class MyClass {
constructor(obj: any) {
for (const prop in obj) {
(this as any)[prop] = obj[prop];
}
}
toString() {
return JSON.stringify(this).replace(
/[\u007f-\uffff]/g,
(character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, '0')}`,
);
}
anotherMethod() {
// ...
}
yetAnotherOne() {
// ...
}
}
const myClass1 = new MyClass({ hello: 'world' });
// casting to any:
console.log((myClass1 as any).hello); // world
console.log(`JSON: ${myClass1}`); // {"hello":"world"}
console.log(myClass1.anotherMethod());
const myClass2 = new MyClass({ foo: 'bar' });
// casting to { foo: string } via unknown:
console.log((myClass2 as unknown as { foo: string }).foo); // bar
console.log(myClass2.yetAnotherOne());
I fail to see why you'd want to do this, but there you go.
Upvotes: 2