Null Object spread horror story
Tags: javascript, webdev, programming
Published: 2021-11-07 10:25:15

While most of the modern frontend engineers use object spread syntax a lot in their code, we all overcome some simple details and underlying mechanisms of how it actually works.

With a first look, this code looks like something that would break right?

```jsx
/*
object? = {...123} // => {} Wait what? Is this valid??
object? = {...undefined} // => {} Um, wat?
object? = {...null} => // {} Please stop
object? = {...false} => // {} Ok I'm done, bye javascript
object? = {...'Smallpdf'} // => {0: "S", 1: "m", 2: "a", 3: "l", 4: "l", 5: "p", 6: "d", 7: "f"}
*/

// Did we break javascript??!
```

Probably we would expect a `TypeError` here. But we must not forget that `...` is a syntax code, not an operator. So the result of it depends on the surrounding context. It behaves differently if it's in an array (`[...myArr]`), in an object (`{...myObj}`), or in a function argument list (`myFunc(arg1, ..restArgs`)

So let's see what happens *exactly* when it is used inside an object.

According to TC39, object spread initializer is [a syntactic sugar](https://github.com/tc39/proposal-object-rest-spread/blob/master/Spread.md) on top of `Object.assign`. So the next logical step is to see how the `Object.assign` *should* work, as instructed by the [ECMAscript spec.](https://262.ecma-international.org/6.0/#sec-object.assign)

![Screenshot from ECMAScript spec explaining how Object.assign should work](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2omyt1mg2d8p0ev43r98.png)
 

In our case, when using the `{...something}` syntax, the object expression (`{}`) is the `target` so it's a newly created object and `sources` is whatever we pass after the `...` syntax, so in our case it's `something`

Now if `something` is `null` or `undefined` we can see an explicit instruction of how `Object.assign` should handle this, treat it like an empty `List` so our end result will just ignore it. This explains why `{...undefined}` and `{...null}` returns an empty object and doesn't crash in any way.

But what happens with `false` `123` and `'Smallpdf'`? Let's go back to the ECMAscript spec

After explicitly handling `undefined` and `null` cases it concludes with the next steps:

![Screenshot from ECMAScript spec explaining what should happen when value is null or undefined](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2zt8l268dgbumrgak8x4.png)
 

So we see that for other types of arguments, (except `null` or `undefined`) the spec uses the `ToObject` abstract operation, to convert the value to an object and if the return value is not `undefined` it will try to use the enumerable properties of the result. Keep in mind that `ToObject` conversions are [described in the table below](https://262.ecma-international.org/6.0/#sec-toobject):

![Screenshot from ECMAScript spec with a detailed table explaining what ToObject conversion should do depending on the argument type](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3ts9dpue29aibr8hxyd3.png)
 

If we try to code this we will get the following results:

```jsx
// ToObject conversion
const NumberObject = new Number(123);
const BooleanObject = new Boolean(false);
const StringObject = new String('Smallpdf');

// Get properties for each items, and return enumerable properties to our object

Object.getOwnPropertyDescriptors(NumberObject)
// => {}
// So object? = {...123} => {} makes sense

Object.getOwnPropertyDescriptors(BooleanObject)
// => {}
// object? = {...false} => {} yup

Object.getOwnPropertyDescriptors(StringObject)
/* =>
0: {value: "S", writable: false, enumerable: true, configurable: false}
1: {value: "m", writable: false, enumerable: true, configurable: false}
2: {value: "a", writable: false, enumerable: true, configurable: false}
3: {value: "l", writable: false, enumerable: true, configurable: false}
4: {value: "l", writable: false, enumerable: true, configurable: false}
5: {value: "p", writable: false, enumerable: true, configurable: false}
6: {value: "d", writable: false, enumerable: true, configurable: false}
7: {value: "f", writable: false, enumerable: true, configurable: false}
length: {value: 8, writable: false, enumerable: false, configurable: false}

*/

// So according to the spec, we take only the `enumerable: true` properties
// from this object. Finally we use their `keys` (0, 1, 2, 3, 4, 5, 6, 7)
and their `value` ('S', 'm', 'a', 'l', 'l', 'p', 'd', 'f') and add them
into our new object.

// object? = {...'Smallpdf'} // => {0: "S", 1: "m", 2: "a", 3: "l", 4: "l", 5: "p", 6: "d", 7: "f"}
// it all makes sense now
```

Javascript surely is weird, but if we follow the spec, it all makes sense! 🌈 🎉