Why does JavaScript convert an array of one string to a string, when used as an object key?
All the object keys are string, so it eventually convert everything you place inside [] (Bracket notation)
to string, if it's an expression it evaluates the expression and convert it's value to string and use as key
console.log(['foo'].toString())
Have a look at this example to understand, here [a]
eventually converts a toString using a.toString()
and then set it as key to b
object
let a = { a : 1}
let b = {
[a] : a
}
// object converted to string
console.log(a.toString())
// object built using [] computed property access
console.log(b)
How can i stop this
In practical scenarios you should never do this, but just to illustrate, you can intercept or override the toString
method of your object and return value as string with []
around:
let a = { foo: 1, bar: 2 }
let b = ['foo']
b.toString = function() {
let string = this.join(',')
return "[" + string + "]"
}
console.log(b.toString())
console.log(a[b])
When using an array as a key, javascript call the 'toString()' method of that array, and then try to find the stringified version of the array as the key. And if you call ['foo'].toString()
you see this method returns "foo"
.
Why does JavaScript convert ['foo'] -> 'foo' when used as a key?
Does anyone out there know the reason?
Any time there is confusion as to why JavaScript acts in a way which may be unexpected, then looking at the language definition is the surefire way to exactly figure out what happened.
https://www.ecma-international.org/ecma-262/10.0/ is the most current language definition at the time of posting this.
First, you will want to find the area pertaining to Array access. It is in language lingo though.
12.3.2.1 Runtime Semantics: Evaluation
MemberExpression : MemberExpression [ Expression ]
...
3. Let propertyNameReference be the result of evaluating Expression.
4. Let propertyNameValue be ? GetValue(propertyNameReference).
6. Let propertyKey be ? ToPropertyKey(propertyNameValue).
So, what is happening here is you are accessing your array (the MemberExpression) using []
with an Expression.
In order to access with []
the Expression will be evaluated, and then GetValue will be called. Then ToPropertyKey will be called.
- propertyNameReference = Evaluate Expression
b
=b
- propertyNameValue = GetValue(propertyNameReference) =
['foo']
- propertyKey = ToPropertyKey(propertyNameValue) =
'foo'
ToPropertyKey, in our situation, leads to ToPrimitive and then to ToOrdinaryPrimitive which states that we should call "toString" on the argument (['foo']
in our case).
This is where the implementation comes in to play. On the implementation side,
The Array object overrides the toString method of Object. For Array objects, the toString method joins the array and returns one string containing each array element separated by commas" MDN - Array toString
When there is only one value in the array, the result will simply be that value.
How can this be prevented?
This is the current way it is implemented. In order to change that, you must either change the default implementation, use detection to prevent the call, or use guidance to prevent the call.
Guidance
Document and enforce calling mechanisms in your code. This may not always be possible. It is at the very least reasonable to expect programmers to not call property access with arrays though.
Detection
This will depend on the current environment. With the most recent iteration of JavaScript, you can use type enforcement to ensure that property access is Number or String. Typescript makes this rather easy (here is a good example). It would essentially just require the access to be defined as:
function ArrayAccess(value: string | number) {
and this would prevent anyone from using the array as an accessor value.
Default Implementation
Changing the default implementation is a terrible idea. It will more than likely cause all sorts of breaking changes, and should not be done. However, just for completeness, here is what it would look like. Primarily I am showing this so you can hopefully recognize it if you see it somewhere and then kill it with fire (or check in some code to fix it if there were no spiders near it).
var arrayToString = [].toString;
Array.prototype.toString = function(){
if(this.length === 1) return;
return arrayToString.call(this);
};
Changing the instance implementation is not much of a better idea either. That is covered by @Code Maniac in a separate answer. "In practical scenarios you should never do this" @Code Maniac states, which I also agree with.