I’m trying to extend a class from an external library that contains methods which use its constructor in the input before starting any operation since it can have multiple types. I want to create a Class that works similarly to it but where the input is sanitized.
class ParentClass {
constructor(value: ParentClass.ValueType) {
// ...
}
methodUsingConstructor(otherValue: ParentClass.ValueType) {
const x = new this.constructor(otherValue)
// do some stuff here with x ...
return result;
}
// ...
}
If I do
class SafeParentClass extends ParentClass {
constructor(value: ParentClass.ValueType) {
// Do some input sanitizing ...
super(sanitizedValue);
}
}
I can use new SafeParentClass(value)
. However, I will get an object with the constructor of the ParentClass. That means that if I do (new SafeParentClass(value1)).methodUsingConstructor(value2)
, then value2
is not sanitized and I get an error.
How can I create a SafeParentClass
such that I can call the methods of the ParentClass
and they will have a constructor that sanitizes the input?
Example:
import { Decimal } from 'decimal.js';
class SafeDecimal extends Decimal {
constructor(value: Decimal.Value) {
if (typeof value === 'string') {
super(parseFloat(value)); // will do super('NaN') if value is not parseable
} else {
super(value);
}
}
}
And then testing it
describe('safeDecimal', () => {
it('should return NaN output with invalid input', () => {
expect(new SafeDecimal('123123').add('').toString()).toEqual('NaN');
});
});
That throws a DecimalError
.
2
Answers
I’m afraid, you’ll have to implement all your methods yourself in the derived class. If the logic of a method of a third-party class doesn’t do exactly what you want, you create a derived class and refer to the original method as
super.method(…)
. With this technique, you can first sanitise the input, and then direct it to the original method.Here’s how you do it, basically:
You’ll have to do that for all methods that you are using.
The problem is the way
decimal.js
is written. It overwrites theconstructor
property on each instance to be, specifically,Decimal
(rather than allowing it to be inherited from the prototype, which would be the normal thing). It does that on what is currently line 4,290 indecimal.mjs
(x.constructor = Decimal;
). The comment on it says it does that to shadow the inherited one "…which isObject
." (The normal thing would be to fix the inherited one insetad.)You can fix that by overriding what
decimal.js
does:Then, your constructor is used and your calculation example works (resulting in
NaN
). I haven’t tested other things.In the normal case, you wouldn’t need this. It’s only necessary because of the specific way
decimal.js
is written.Having said that, you might want to fork it and do the changes you think you need to do in your fork instead.
Finally, you might want to consider doing something more robust than
parseFloat
, sinceparseFloat
will happily return123
for the string"123asdlfkjasldf"
(whereas it would probably be more reasonable to throw an error or returnNaN
). Here’s an answer going into your various options for the tools you might use to build something more robust.