I want to override the toString
without explicitly instantiating an object with new
.
See following example:
class SemicolonArray extends Array {
public toString() {
return this.join(';');
}
}
class Foo {
constructor(arr: SemicolonArray) {
this.arr = arr;
}
arr: SemicolonArray;
}
const foo = new Foo([1,2,3]);
console.log(foo.arr.toString()); // "1,2,3"
I would however expect "1;2;3"
. Is there a way to do this in TypeScript?
Edit:
To clarify further, I would like to do this:
interface Bar {
hello :string
bar() : string
}
class Foo implements Bar {
hello: string
bar() {
return this.hello;
}
}
const foo = { hello: 'hello' } as Foo
foo.bar()
However bar
is not a method. Is there a way to make sure that every object casted as Foo has the bar method?
CodePudding user response:
There are two distinct issues here:
Making array literal syntax create a
SemicolonArray
.Foo
is expecting aSemicolonArray
but TypeScript doesn't complain when you pass in a base array instead, since the shape of a base array matches the shape of aSemicolonArry
.
Making array literal create subclass
There's no way to make an array literal create an instance of an array subclass. Literal notation cannot be overridden. An array literal can only create a standard array.
You could use SemicolonArray.from
:
const foo = new Foo(SemicolonArray.from([1,2,3]));
...but new SemicolonArray(1, 2, 3)
is probably simpler.
Or you could have Foo
convert a base array to a SemicolonArray
:
class Foo {
constructor(arr: SemicolonArray) {
this.arr = arr instanceof SemicolonArray ? arr : SemicolonArray.from(arr);
}
arr: SemicolonArray;
}
// ...
const foo = new Foo([1, 2, 3]);
No error when passing base array
As you've discovered, with your current definition of SemicolonArray
, TypeScript won't complain about a base array being used instead, since SemicolonArray
doesn't define any non-method properties. That's because TypeScript's type system is structural (based on the shape of types; what properties they have and their types), not nominal (based on the name [identity] of types).
That's likely to lead to errors. You can solve it by adding a branding property to SemicolonArray
:
class SemicolonArray extends Array {
readonly __type__ = "SemicolonArray"; // ***
public toString() {
return this.join(";");
}
}
Now, TypeScript won't allow a base array where a SemicolonArray
is expected.
As always with array subclasses, beware that the Array
constructor handles its arguments very idiosyncratically. Your subclass is fine because it doesn't define its own constructor, but if you end up adding one, be sure to handle the complexity.
Re your edit:
However
bar
is not a method. Is there a way to make sure that every object casted asFoo
has thebar
method?
No. In fact, TypeScript doesn't have casting at all. It has type assertions. People sometimes use the term "cast" but it's inaccurate. In a language with casting, a cast can actually transform a value (it doesn't necessarily, but it can). For instance, consider this C code:
double a = 2.4;
int b = (int)a;
printf("%d\n", b); // 2
The cast operation (int)
actually transforms the value: the operand is a
, which is of type double
, but the result of the cast operation is an int
. The bit pattern of the value is actually transformed at runtime from a double
bit pattern to an int
bit pattern, using a conversion process.
Type assertions don't do that. They have no runtime aspect at all, they completely disappear. All that a type assertion does is tell TypeScript that you're sure that the operand is actually a valid instance of the type. The value is still what it was, the only thing that's changed is TypeScript's understanding of what type it is.
If you want to convert something to something else, a type assertion doesn't do it. You have to use a conversion of some kind (like SemicolonArray.from
) that actually produces a new thing of the correct type.
(For the avoidance of doubt: JavaScript has a fair bit of built-in conversion, such as when you do "42" / 2
or "x" 2
. But TypeScript doesn't, other than the things it inherits from JavaScript [like those conversions].)