Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TypeScript new class that extends String

I am currently trying to set up a class "AdvancedString". This class should extend String by methods like e.g. isJSON. Currently it looks like this

class AdvancedString extends String {
    isJSON():boolean{
        var itIs = true;
        try{
            JSON.parse(this.toString());
        } catch (err) {
            itIs = false;
        }       

        return itIs;
    }
}

export{ AdvancedString}

now it doesn't work too bad. If I create the a new instance with "SampleString" I'll get

let sample = new AdvancedString("SampleString");
// ExtendedString {[[PrimitiveValue]]: "SampleString"}

and if I do toString i get back the correct value

sample.toString()
// "SampleString"

However I would like for it to behave like a normal string when i call it directly so

sample === "SampleString"
// should return true and sample.toString() === "SampleString"
// should not be necessary

Is there any way to accomplish this in TypeScript or is this not possible? I would like to use a separate class and not add my method to the prototype of string

like image 285
relief.melone Avatar asked Apr 09 '26 20:04

relief.melone


2 Answers

It is not possible, unfortunately. An object is still an object and is not === to primitive value (string).

Trust me I've spent dozens of hours on this problem, you simply can't do anything.

You can mutate String.prototype.isJSON

String.prototype.isJson = () => 'your logic here'

But that's not very clean solution, unless you are sure that this code stays inside your app only.

P.S. Typescript has nothing to do with this problem.

like image 109
Nurbol Alpysbayev Avatar answered Apr 11 '26 18:04

Nurbol Alpysbayev


=== requires the types to be the same. Instances of your AdvancedString type don't have the same type as either primitive or object strings, and so === will always be false. new String("") === "" is also false, for the same reason; the former is a String object, the latter is a primitive string.

Your AdvancedString is == primitive strings, but only (it seems) if you use class natively; when I try that in the TypeScript playground with TypeScript transpiling the class syntax, it fails. But it works natively:

class AdvancedString extends String {
  isJSON() {
    var itIs = true;
    try {
      JSON.parse(this.toString());
    } catch (err) {
      itIs = false;
    }

    return itIs;
  }
}

const s = new AdvancedString("foo");
console.log(s === "foo"); // false
console.log(s == "foo");  // true

However, you might want to consider the downsides of using AdvancedString, since your strings will be objects, not primitives:

  • Memory impact. Allocating those string objects has a bigger memory impact than using primitive strings.¹
  • Any code receiving one of your strings (for instance, a library you might be using) that does a typeof check will not see them as (primitive) strings. So an API accepting a string or an options object, for instance, will likely be misled if you pass it an AdvancedString instance. TypeScript may not be able to warn you when this happens, depending on how the library is annotated (string|object, for instance, wouldn't catch it).

¹ Out of curiousity, I did this:

const s = "**y";
const a = Array.from({length: 100000}, () => new String("x" * s));

...on an otherwise-blank page. I ended up with 100,005 String instances in memory (I guess there were 5 from somewhere else) occupying 3,200,160 bytes. In contrast, this:

const s = "**y";
const a = Array.from({length: 100000}, () => "x" + s);

...only increased the number of primitive strings from 3,830 (281,384 bytes) to 27,533 (1,040,120 bytes); apparently some (but not all) of them were reused.

So that's ~741kB for primitives vs. ~3MB for objects, a bit over four times the memory.

That's just one synthetic test, but you get the idea.

like image 45
T.J. Crowder Avatar answered Apr 11 '26 18:04

T.J. Crowder