0

I encountered a weird JS behavior during a mock interview and I'm just curious why this occurs. Here is a minimal reproduce case:

class Association {}
class Document {}
class Post extends Document {}
class User extends Document {}

let [ u1, u2 ] = [new User(), new User()]
let [ p1, p2, p3 ] = [ new Post(), new Post(), new Post() ]

let posts = new Association(User, Post, "posts")

let database = {
  [u1]: { [posts]: [ p1, p2 ] },
  [u2]: { [posts]: [ p3 ] },
}

console.log("HERE", database[u1][posts]) // [ Post {} ]
Why is the result only 1 post? Shouldn't it be 2 as defined in the database?

6
  • 2
    What is your question? Commented Oct 19, 2022 at 23:19
  • 1
    I wouldn't use Document as a class name since it is already a global property Commented Oct 19, 2022 at 23:19
  • 2
    Object properties must be strings. If you use something other than a string, it will be stringified. Both your User classes will serialise to the same value. If you want rich keys, use a Map Commented Oct 19, 2022 at 23:21
  • 1
    Would overriding the toString or hashCode method fix this? Commented Oct 19, 2022 at 23:24
  • 1
    See Using an object as a property key in JavaScript and Javascript object literal - possible to add duplicate keys? Commented Oct 19, 2022 at 23:24

1 Answer 1

3

Object property names can ONLY be strings or Symbols. We can ignore Symbols here. If you actually want to use objects as a key, then you can use a Map or a Set.

[u1]: { [posts]: [ p1, p2 ] },

This ☝️ line adds a property named [Object object] to the database object. It sets the value associated with that property to a new object that itself is initialised with a property named [Object object] whose value is set to be an array containing p1 and p2.

[u2]: { [posts]: [ p3 ] },

This ☝️ line overwrites the value of the property named [Object object] with a reference to a new object that has one property named [Object object] that has a value of an array containing p3.

When referring to an object property with the bracket syntax [<expression>], the expression is first evaluated, and the result is then coerced (because JS is weakly typed) to a string, if need be, to form the property name.

u1 and u2 are objects. Using an object as a property key will, by default, result in a call to the abstract internal operation OrdinaryToPrimitive with the hint 'string'. This will invoke Object.prototype.toString(), and this will, by default, result in the string '[Object object]'.

Note that you can override various aspects of this coercion behavior; for example, by implementing your own Symbol.toStringTag getter method: 👇

class Association {}
class Document {}
class Post extends Document {}
class User extends Document {}

let [ u1, u2 ] = [new User(), new User()]
let [ p1, p2, p3 ] = [ new Post(), new Post(), new Post() ]
let posts = new Association(User, Post, "posts")

Object.defineProperty(u1, Symbol.toStringTag, {
  get() { return 'u1' }
})

let database = {
  [u1]: { [posts]: [ p1, p2 ] },
  [u2]: { [posts]: [ p3 ] },
}

console.log("HERE", JSON.stringify(database))

More details here.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.