Ryan Kennedy
Ryan Kennedy

Reputation: 3615

Can I rely on the string representation of an ES6 `Symbol`?

I'm working on an ES6 app that sends some of its data over the network. Part of this involves identifiers that are implemented as ES6 Symbols. For example:

const FOO = Symbol('foo');

Calling Foo.toString() yields Symbol(foo). When I pass these over the network, I'd like to pass it as just foo. However, to my knowledge, there is no way to extract foo from Symbol(foo) other than to pull it out with a regular expression (specifically, /^Symbol\((.*)\)$/).

Should I rely on the regular expression always matching? Or is it possible that future updates to ES6 will break this? If I can't rely on the regex matching then I'll just send it over the wire as Symbol(foo).

Upvotes: 4

Views: 248

Answers (2)

Sebastian Simon
Sebastian Simon

Reputation: 19485

According to the spec it’s always "Symbol(" + description + ")".

Symbol.prototype.toString returns a string from an internal method call to SymbolDescriptiveString(sym):

Let desc be sym’s [[Description]] value.
If desc is undefined, let desc be the empty string.
[…]
Return the string-concatenation of "Symbol(", desc, and ")".

Now, updating this answer in 2019, you have two options:

  1. Use (or polyfill) Symbol.prototype.description, which is part of ECMAScript 2019 and is supported by all modern JavaScript engines:

    const foo = Symbol("foo"),
      bar = Symbol.for("bar"),
      iter = Symbol.iterator;
    
    console.log(foo.description); // "foo"
    console.log(bar.description); // "bar"
    console.log(Symbol.iterator.description); // "Symbol.iterator"

  2. Or use Symbol.prototype.toString like so:

    const foo = Symbol("foo"),
      bar = Symbol.for("bar"),
      iter = Symbol.iterator;
    
    console.log(foo.toString().slice(7, -1)); // "foo"
    console.log(bar.toString().slice(7, -1)); // "bar"
    console.log(iter.toString().slice(7, -1)); // "Symbol.iterator"
    
    // Or without “magic numbers”:
    console.log(foo.toString().slice("Symbol(".length, -")".length)); // "foo"
    console.log(bar.toString().slice("Symbol(".length, -")".length)); // "bar"
    console.log(iter.toString().slice("Symbol(".length, -")".length)); // "Symbol.iterator"

Since the strings surrounding the description are fixed, slice is a good option to use, especially because a symbol description could itself contain parentheses, line breaks, the string "Symbol", etc, and the . in the regular expression won’t match line breaks for example.

Upvotes: 4

Bergi
Bergi

Reputation: 664434

The only thing to add to @Xufox' answer is that Symbol.prototype.toString could be compromised (overwritten), in which case it might return something else. Given that that's hardly a scenario you'll want/need to consider, it should be fine; go for .toString().slice(7, -1);.


An alternative solution you might want to consider would be to use a global symbol. If you are going to pass your data around, and will require to prevent name collisions anyway, this would be a suitable use case (assuming you're not writing a library that's supposed to be used by third parties).

You'd use

const FOO = Symbol.for("foo");
//                 ^^^

and could then get the symbol's name (which is also its description) back through

Symbol.keyFor(FOO) // "foo"

Upvotes: 3

Related Questions