skip to Main Content

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


  1. 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:

    class ThirdPartyClass {
      method(argument) {
        // the logic here unsafely uses the argument
      }
    }
    
    class DerivedClass extends ThirdPartyClass {
      override method(argument) {
        const safeArgument = sanitize(argument)
    
        return super.method(safeArgument)
      }
    }
    

    You’ll have to do that for all methods that you are using.

    Login or Signup to reply.
  2. The problem is the way decimal.js is written. It overwrites the constructor 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 in decimal.mjs (x.constructor = Decimal;). The comment on it says it does that to shadow the inherited one "…which is Object." (The normal thing would be to fix the inherited one insetad.)

    You can fix that by overriding what decimal.js does:

    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);
            }
            this.constructor = SafeDecimal; // <=== Here
        }
    }
    

    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, since parseFloat will happily return 123 for the string "123asdlfkjasldf" (whereas it would probably be more reasonable to throw an error or return NaN). Here’s an answer going into your various options for the tools you might use to build something more robust.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search