October 8, 2024

Typescript object initialization and instanceof operator caveat

Hello guys! I’m writing today about some caveat I’ve encountered while writing some Typescript code for an API. Basically, I was trying to catch a set of errors and if one error was an instance of ErrorBdo, the execution would pass onto another context to perform something different. And for that I was using the instanceof operator which comes in Typescript (it exists in Javascript as well, but didn’t have the chance of using it).

How I was trying to use it

Coming from a C# strong typed background, I had quite some nerves to joggle with the quirks of Javascript and subsequently Typescript. I managed to overcome most challenges and I reached a level where I thought a code like this would produce expected results:


class SimpleStuff {
  message: string;
  code: number;
  hasPriority: boolean;
}

const simple1: SimpleStuff = {
  message: "Hello",
  hasPriority: false,
  code: 23,
  // additionalProperty: 123, // error
}

console.log(`simple1 instanceof SimpleStuff: ${simple1 instanceof SimpleStuff}`) // Expected true

Well, to my surprise, the instanceof operator returned false and I was in shock and awe! I have a class instance initialized and converted to the expected type, still the instanceof operator yields negative results. Also tried another approach to make it work, but with no luck:


const simple2 = <SimpleStuff> {
  message: "Simple",
  hasPriority: true,
  code: 53,
  additionalProperty: 123, // no error
}

console.log(`simple2 instanceof SimpleStuff: ${simple2 instanceof SimpleStuff}`) // Expected true

Then I thought that this is crazy, why isn’t it working at all? To make a long story short, it seems it’s some Javascript quirk that relies on Object prototype and only has information about the instance if it was created with the new keyword. Hence, any inline initialization like above will not work with the instanceof operator, but will be recognized as the right data type for other purposes. If you want the instanceof operator to work, you’ll have to use this approach evidently:


const simple3 = new SimpleStuff();
simple3.message = "New init";
simple3.hasPriority = false;
simple3.code = 65;
// simple3.additionalProperty = 123; // error
simple3['additionalProperty'] = 123;

console.log(`simple3 instanceof SimpleStuff: ${simple3 instanceof SimpleStuff}`) // Finally, the expected result - true

Problem solved, code pushed but …

As you might know, I like elegant code and witty constructions. And I am stubborn sometimes… Why can’t I use inline initialization and also the instanceof properly? There has to be a way. I like to create an object inline, it’s one of the things I adore about this ecosystem. Well yes, there is a way, actually two ways which I will exemplify here.

The constructor way

This approach simply involves adding an optional parameter in the class constructor so that it will initialize the properties. This uses a special Typescript generic class, the Partial class, which will make the properties optional. You can omit this, it’s up to you.


class SimpleStuffWithPartial {
  constructor(init?: Partial<SimpleStuffWithPartial>) {
    Object.assign(this, init);
  }

  message: string;
  code: number;
  hasPriority: boolean;
}

Now that we have this in place, let’s see how to use it:


const simple1WithPartial: SimpleStuffWithPartial = {
  message: "Hello",
  hasPriority: false,
  code: 23,
}

const simple2WithPartial = new SimpleStuffWithPartial({
  message: "Simple",
  hasPriority: true,
  code: 53,
});
console.log(`simple1WithPartial instanceof SimpleStuffWithPartial: ${simple1WithPartial instanceof SimpleStuffWithPartial}`) // false
console.log(`simple2WithPartial instanceof SimpleStuffWithPartial: ${simple2WithPartial instanceof SimpleStuffWithPartial}`) // true

As you can notice, the simple1WithPartial object was initialized like before, hence the instanceof operator will return false. But the simple2WithPartial was initialized through the constructor and the values of the properties in the inline object will be automatically assigned to our instance and we also benefit from the instanceof operator returning true, like we would expect.

What I don’t like about this approach is that you need to alter each class to add this parameter. If you have a bigger app, it’s insane to do this. Instead I recommend the following approach.

The elegant generic helper function way

This approach doesn’t require you to modify your data classes at all! Simply add it in your project and use it whenever necessary. It looks as gentle as it can be:


function from<OType>(creator: new() => OType, props?: OType): OType {
  const t = new creator();

  Object.assign(t, props ?? {});

  return t;
}

The creator parameter may look scary but in fact it’s simple: you know that a constructor in Javascript is purely a function and if you are familiar with arrow functions you’ll understand that we simply need to pass the class type for which the constructor will return the OType generic value. That’s it!

The beauty of this is that it uses generics hence activating the Typescript’s power in this regard, it can be used with any class, it’s not far from the elegance of inline object initialization, and it’s type safe. Let’s prove this:


const simpleFromFunction = from(SimpleStuff, {
  message: "Simple from helper function",
  hasPriority: true,
  code: 53,
  // additionalProperty: 123, // error
});
console.log(`simpleFromFunction instanceof SimpleStuff: ${simpleFromFunction instanceof SimpleStuff}`) // true !

Pretty neat I would say 😄 You can find the code for this sample here on Github.

Conclusion

Almost no article without conclusion; so I hope that I helped you understand better how this instanceof operator behaves and very important as well, how to deal with the problem in an elegant way. There might be other ways as well, if you have one in mind, just drop a comment below!

Thanks for reading, I hope you found this article useful and interesting. If you have any suggestions don’t hesitate to contact me. If you found my content useful please consider a small donation. Any support is greatly appreciated! Cheers  😉

afivan

Enthusiast adventurer, software developer with a high sense of creativity, discipline and achievement. I like to travel, I like music and outdoor sports. Because I have a broken ligament, I prefer safer activities like running or biking. In a couple of years, my ambition is to become a good technical lead with entrepreneurial mindset. From a personal point of view, I’d like to establish my own family, so I’ll have lots of things to do, there’s never time to get bored 😂

View all posts by afivan →