DEV Community

loading...

Using JavaScript Symbol.toStringTag for objects types description

cherif_b profile image Cherif BOUCHELAGHEM ・3 min read

A day ago I asked my developers friends:

How to know a JavaScript object literal type?

Most of the answers of the answers suggested to use instanceof, while this answer works for objects created by using a constructor, it can not work with objects literals or maybe for some reasons developers don't export the object constructor from the module but they provide a factory with object initializer instead.

The use case:

The question was about the following use case:

const user = {
   username: 'johndoe',
   firstname: 'John',
   lastname: 'Doe'
};
Enter fullscreen mode Exit fullscreen mode

It is hard to determine if the object above is in certain type without checking the presence of all the properties, imagine if we have a more than 10 properties or more, it would be exhausting, right?
It is obvious it would be good if we can know that the object is indeed in User type just by a single call.

Using Object.prototype.toString() gives the object type inside square brackets for the built-in types/objects:

console.log(Object.prototype.toString.call(1)); // -> '[object Number]'
console.log(Object.prototype.toString.call('Cherif')); // -> '[object String]'
Enter fullscreen mode Exit fullscreen mode

Or:

const one = 1;
const name = 'Cherif';

console.log(one.toString()); // -> '[object Number]'
console.log(name.toString()); // -> '[object String]'
Enter fullscreen mode Exit fullscreen mode

But for a developer defined objects it gives a [object Object] result, a generic object:

const user = {
   username: 'johndoe',
   firstname: 'John',
   lastname: 'Doe'
};

console.log(user.toString()); // -> '[object Object]'
console.log(Object.prototype.toString.call(user)); // -> '[object Object]'
Enter fullscreen mode Exit fullscreen mode

Symbol.toStringTag to the rescue

Fortunately with the introduction of ES6 Symbols a number of built-in symbols had seen the day make it possible to describe custom objects type by overriding Object.prototype.toString() using Symbol.toStringTag, so to describe a type for our user object we can do the following:

const user = {
   username: 'johndoe',
   firstname: 'John',
   lastname: 'Doe',
   get [Symbol.toStringTag]() {
       return 'User'; // The string tag description
   }
};

console.log(user.toString(user)); // -> '[object User]'
console.log(Object.prototype.toString.call(user)); // -> '[object User]'
Enter fullscreen mode Exit fullscreen mode

Read the string using Symbol.toStringTag:

I would advice to make the check using the Symbol property by using user[Symbol.toStringTag], like this we have the possibility to override toString function for other purposes, and we can just have User string from the string tag instead of [object User] example:

const user = {
   username: 'johndoe',
   firstname: 'John',
   lastname: 'Doe',
   get [Symbol.toStringTag]() {
       return 'User'; // The string tag description
   },
   toString() {
       return `${this.firstname} ${this.lastname}`;
   }
};

console.log(user[Symbol.toStringTag]); // -> "User"
console.log(user.toString()); // -> "John Doe"
Enter fullscreen mode Exit fullscreen mode

All the code examples are in the following CodePen:

See the Pen Symbol.stringTag by Mohamed Cherif Bouchelaghem
(@cherifGsoul) on CodePen.

I hope you find this article useful and helpful, let me know your thoughts in the comments below.

Discussion (10)

Collapse
ninofiliu profile image
Nino Filiu

Nice, I didn't know about this!

But I wouldn't trust this trick for type-safety checking

const user = {
  name: 'John',
  [Symbol.toStringTag]: 'User',
};
delete user.name;
if (user[Symbol.toStringTag] === 'User') {
  // cool, `user` is a User, it must have the `name` property!
  user.name.length; // fails
}
Enter fullscreen mode Exit fullscreen mode

whereas in TypeScript, any operation changing the shape of an object outside of its type is prevented

type User = { name: string }
const user = { name: 'john' }
delete user.name; // Error: The operand of a 'delete' operator must be optional.(TS2790)
Enter fullscreen mode Exit fullscreen mode
Collapse
aminnairi profile image
Amin • Edited
type User = { name: string }
const user = { name: 'john' }

Object.defineProperty(user, "name", {
  value: undefined
});

alert(user.name.length); // Cannot read property 'length' of undefined (in JavaScript after transpilation)
Enter fullscreen mode Exit fullscreen mode

This compiles just fine in TypeScript and even gives an error in the playground when run.

Collapse
ninofiliu profile image
Nino Filiu

Sure, but come on, that's just deliberately trying to deceive TypeScript at this point ^^

Collapse
cherif_b profile image
Cherif BOUCHELAGHEM Author

I agree, however there's other ways to prevent this like freezing or using Proxies.

Collapse
levideang29 profile image
LeviDeang29

Very interesting. How I wish this was a built-in feature.

Collapse
cherif_b profile image
Cherif BOUCHELAGHEM Author

It is built-in you just need to override the Symbol.

Collapse
levideang29 profile image
LeviDeang29

No I mean built-in as in you don't have to code anything else, just do a user.toStringTag() and it'll return [object user].

Collapse
ayoubroot profile image
ayoub benayache

interesting, ready i like the way how did you explain it, thnaks

Collapse
haouihamza profile image
hamza haoui

thank you πŸ™πŸ»

Collapse
khaldiamer profile image
Khaldi Ameur

Very useful. Thanks!

Forem Open with the Forem app