# Improved and safer alternative to Object.create(null)

**URL:** <https://es.discourse.group/t/improved-and-safer-alternative-to-object-create-null/644>\
**Category:** 💡 Ideas\
**Created:** [February 15, 2021, 2:34pm UTC](https://es.discourse.group/t/improved-and-safer-alternative-to-object-create-null/644 "2021-02-15T14:34:31Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![theScottyJam](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/thescottyjam/32/616_2.png) [@theScottyJam](https://es.discourse.group/u/theScottyJam)\
**Post date:** [February 15, 2021, 2:34pm UTC](https://es.discourse.group/t/improved-and-safer-alternative-to-object-create-null/644/1 "2021-02-15T14:34:31Z")

</div>

Even though the newer `Map` class has been around for a while now, plain old javascript objects are still often being used as a map, perhaps due to the native syntax support they have, making them much less verbose to use. However, one has to be careful when using plain objects as maps as all objects will inherit properties, such as `toString()`, that shouldn't be in the mapping. For this reason, it has become a common pattern to create an object without a prototype using `Object.create(null)`, but even this still has a few pitfalls of its own (albeit, much more minor).

One such pitfall is that these objects do not know how to convert themselves to a primitive, so they will throw errors in scenarios such as these:

```javascript
map = Object.create(null)
console.log(`Value: ${map}`) // Uncaught TypeError: Cannot convert object to primitive value
console.log(0 + map) // Uncaught TypeError: Cannot convert object to primitive value

```

I'm proposing that we provide a more user-friendly way to use objects as maps: `Object.createMap()` - I'll refer to these kinds of maps as "Property Maps" to distinguish them from instances of the Map class.

`Object.createMap()` is intended to replace `Object.create(null)` usage. It will return an object with a very slim prototype (that itself inherits from null, not `Object.prototype`). This prototype will never contain string keys, but it does define some symbols. For example, it can provide a definition for `Symbol.toPrimitive` to allow string/number coercion to behave in a normal way:

```javascript
const map = Object.createMap()
console.log('As String: ' + map) // As String: [object PropertyMap]
console.log(+map) // NaN

```

`Object.createMap()` could conveniently provide the option to convert an existing object into a property map, by passing in that object as the first parameter.

```javascript
const map = Object.createMap({ a: 1, b: 2 })
console.log(map.a) // 1

```

We could define `Symbol.iterator`, to let one iterate over the entries of the property map.

```javascript
const map = Object.createMap({ a: 1, b: 2 })
for (const [key, value] of map) {
  console.log(`${key}=${value}`) // 'a=1' and 'b=2'
}

```

Providing `Symbol.iterator` in conjunction with the [iterator-helpers](https://github.com/tc39/proposal-iterator-helpers) proposal could provide some powerful tools to lazily work with an object's entries.

The following would be an example implementation of `Object.createMap()`

```javascript
const propMapPrototype = Object.create(null, {
  [Symbol.toPrimitive]: {
    enumerable: false,
    value(hint) {
      if (hint === 'number') return NaN
      return `[object ${this[Symbol.toStringTag]()}]`
    },
  },
  [Symbol.toStringTag]: {
    enumerable: false,
    value() {
      return 'PropertyMap'
    },
  },
  [Symbol.iterator]: {
    enumerable: false,
    *value() {
      for (const key in this) {
        if (Object.prototype.hasOwnProperty.call(this, key)) {
          yield [key, this[key]]
        }
      }
    },
  },
})

Object.defineProperty(Object, 'createMap', {
  writable: true,
  configurable: true,
  value(properties = {}) {
    return Object.assign(Object.create(propMapPrototype), properties)
  },
})

```

---

<div class="post-metadata">

**Author:** ![claudiameadows](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/claudiameadows/32/126_2.png) [@claudiameadows](https://es.discourse.group/u/claudiameadows)\
**Post date:** [February 15, 2021, 5:30pm UTC](https://es.discourse.group/t/improved-and-safer-alternative-to-object-create-null/644/2 "2021-02-15T17:30:41Z")

</div>

What does this provide that `Object.fromEntries(map)` doesn't?

---

<div class="post-metadata">

**Author:** ![ljharb](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/ljharb/32/8_2.png) [@ljharb](https://es.discourse.group/u/ljharb)\
**Post date:** [February 15, 2021, 5:45pm UTC](https://es.discourse.group/t/improved-and-safer-alternative-to-object-create-null/644/3 "2021-02-15T17:45:08Z")

</div>

I see the advantage - it would not inherit from Object.prototype and thus would lack all the string methods on Object.prototype - but I'd want to see a lot of userland usage of this pattern as evidence that people actually want these semantics.

---

<div class="post-metadata">

**Author:** ![theScottyJam](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/thescottyjam/32/616_2.png) [@theScottyJam](https://es.discourse.group/u/theScottyJam)\
**Post date:** [February 15, 2021, 6:53pm UTC](https://es.discourse.group/t/improved-and-safer-alternative-to-object-create-null/644/4 "2021-02-15T18:53:27Z")

</div>

I can think of a couple off of the top of my head.

Node's [querystring.parse()](https://nodejs.org/api/querystring.html#querystring_querystring_parse_str_sep_eq_options) will return an object with a null prototype.  
Things like [express's urlencoder](http://expressjs.com/en/5x/api.html#express.urlencoded) middleware uses Node's querystring.parse() under the hood, and so will also return such an object.

I'm sure there are more examples like this, but I guess I don't _know_ that, so it would be interesting to know how much it gets used in the wild.

---

<div class="post-metadata">

**Author:** ![theScottyJam](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/thescottyjam/32/616_2.png) [@theScottyJam](https://es.discourse.group/u/theScottyJam)\
**Post date:** [February 15, 2021, 7:42pm UTC](https://es.discourse.group/t/improved-and-safer-alternative-to-object-create-null/644/5 "2021-02-15T19:42:36Z")

</div>

@claudiameadows

`Object.fromEntries()` will still return an object with the prototype `Object.prototype`, so you still have to be careful not to accidentally access inherited properties.

For example:

```javascript
const mapOfActions = Object.fromEntries([
  ['action1', () => doAction1()],
  ['action2', () => doAction2()],
])

function doAction(userProvidedKey) {
  const action = mapOfActions[userProvidedKey]
  console.log(action && action())
}

```

Spot the bug?

What if userProvidedString was 'toString'? I'm sure you could imagine worse versions of this where javascript is running on the server, and the user-provided string created a security vulnerability. This is avoidable if the writer of this code snippet either used a `Map` instance, or followed the common advice to check for property existence with `Object.prototype.hasOwnProperty.call()`, but it's easy to forget these checks, and it's cumbersome to do them.

Now the same example with `Object.createMap()`

```javascript
const mapOfActions = Object.createMap({
  action1: () => doAction1(),
  action2: () => doAction2(),
})

function doAction(userProvidedKey) {
  const action = mapOfActions[userProvidedKey]
  console.log(action && action())
}

```

Now calling doAction with 'toString' would correctly result in 'undefined' - the behavior that was supposed to happen when keys were supplied that were not in the map.

---

<div class="post-metadata">

**Author:** ![rumkin](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/rumkin/32/598_2.png) [@rumkin](https://es.discourse.group/u/rumkin)\
**Post date:** [February 17, 2021, 12:15am UTC](https://es.discourse.group/t/improved-and-safer-alternative-to-object-create-null/644/6 "2021-02-17T00:15:11Z")

</div>

Why should it be a method and not a new class? If it would be a class, it could be created with Object.create, inherited, extended, etc. It plays well with native JS library and seems to be straightforward with current JS OOP model.

There could be RawObject, some class derived from null which has no methods and could be inherited using `extend` and its ancestor a MapObject. This is an example implementation of suggested behavior:

```javascript
class RawObject {}

Object.setPrototypeOf(RawObject.prototype, null)

```

MapObject implementation:

```javascript
class MapObject extends RawObject {
  static from(props) {
    return Object.assign(new this(), props)
  }

  static fromEntries(entries) {
    // ...
  }
  
  [Symbol.toStringTag]() {
    return this.constructor.name
  }

  *[Symbol.iterator]() {
    for (const key in this) {
       if (Object.prototype.hasOwnProperty.call(this, key)) {
         yield [key, this[key]]
       }
    }
  }
}

```

Now static constructor and Object.create works fine:

```javascript
// Create with Object.create
const mapObject = Object.assign(Object.create(MapObject.prototype), {
  action1: () => "action 1",
  action2: () => "action 2",
})

// Create with static constructor
const mapObject = MapObject.from({
  action1: () => "action 1",
  action2: () => "action 2",
})

console.log(mapObject.action1()); // => "action 1"
console.log(mapObject.action2()); // => "action 2"

for (const [k, v] of mapObject) {
  console.log(k, v)
}

```

Such solution has several benefits:

1. It's possible to check such map with `instanceof`.
2. It could be inherited to modify the behavior, e.g. to accept only exact key names or values.
3. RawObject could be used to implement other custom objects.
4. RawObject can be inherited, while null can't.

I believe this is a good idea to do so. But map object could be implemented as a library. And then reimplemented as built-ins, if it there will be a need. So I'd like to see RawObject-alike built-in as a building block.

---

<div class="post-metadata">

**Author:** ![theScottyJam](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/thescottyjam/32/616_2.png) [@theScottyJam](https://es.discourse.group/u/theScottyJam)\
**Post date:** [February 17, 2021, 4:12am UTC](https://es.discourse.group/t/improved-and-safer-alternative-to-object-create-null/644/7 "2021-02-17T04:12:57Z")

</div>

I like the idea of using classes - there are a lot of pros to making this a class instead, as you mentioned. Plus, it makes it more consistent with how other types are handled in javascript.

There is one annoying pitfall though - the class keyword seems to automatically cause a "constructor" property to be added to the prototype.

Let's take the suggested RawObject class:

```javascript
class RawObject {}
Object.setPrototypeOf(RawObject.prototype, null)

const rawObj = new RawObject()
console.log(rawObj.constructor) // [class RawObject]

```

Whoops! This seems to be fixable though, we can just remove the constructor property from the prototype after creating the class.

```javascript
class RawObject {}
Object.setPrototypeOf(RawObject.prototype, null)
delete RawObject.prototype.constructor

const rawObj = new RawObject()
console.log(rawObj.constructor) // undefined

```

That's better! Now let's try to subclass it:

```javascript
class RawObject {}
Object.setPrototypeOf(RawObject.prototype, null)
delete RawObject.prototype.constructor

class CustomClass extends RawObject {}

const customObj = new CustomClass()
console.log(customObj.constructor) // [class CustomClass extends RawObject]

```

Darn! Each subclass also has to explicitly delete the constructor property off of its prototype. I don't like the idea of having to make users of this class remember to delete the constructor property when they wish to subclass it.

I can't think of any good way around this issue. There might be, so please share if anyone can think of anything, but maybe the best solution would be to just not define this with an es6 class, making those who wish to subclass it have to do so with the lower-level prototype functions, such as `Object.create()`

---

<div class="post-metadata">

**Author:** ![rumkin](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/rumkin/32/598_2.png) [@rumkin](https://es.discourse.group/u/rumkin)\
**Post date:** [February 18, 2021, 12:15pm UTC](https://es.discourse.group/t/improved-and-safer-alternative-to-object-create-null/644/8 "2021-02-18T12:15:22Z")

</div>

Yep, this is a good point. I think you've highlighted a bigger JS problem, which should be solved instead of making myriads of tiny patches.

I'm sure such map could be implemented as a standalone library. And if it will be popular, it will receive support from community and then it should be adopted to the standard. Maybe there are some. Have you looked at NPM?

---

<div class="post-metadata">

**Author:** ![theScottyJam](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/thescottyjam/32/616_2.png) [@theScottyJam](https://es.discourse.group/u/theScottyJam)\
**Post date:** [February 18, 2021, 3:41pm UTC](https://es.discourse.group/t/improved-and-safer-alternative-to-object-create-null/644/9 "2021-02-18T15:41:49Z")

</div>

You know, I just had a change of heart with this proposal.

I initially made this proposal in part because of [this section](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create#custom_and_null_objects) on mdn explaining some of the pitfalls of using `Object.create(null)`. I had realized that a few of those pitfalls were fixable with the help of `Symbol.toPrimitive`, and the rest were basically telling you that things like ".toString()" won't be found on the object - which is the whole point.

This `Object.createMap()` proposal only provided marginal improvements over `Object.create(null)` - the biggest one is the fact that it can be converted to a (useless) primitive, making code that expects this ability to not break (i.e. some sort of not-so-well-designed logging function or something). I knew that these improvements were minor enough that it could potentially be decided that it wasn't worth it to create a function just for this, but I thought the discussion was worth having to see what other people's thoughts were on the matter.

I have recently realized that symbols were purposely implemented to have the exact same issue I'm trying to avoid with null-prototypes - they also throw an error when they get converted to a number or string. No one seems to be complaining about that, and everyone's codebases continue to work.

So, thanks everyone for the feedback.

I'm now against this proposal too.
