How to properly log objects in JavaScript?
Tags: javascript, webdev
Published: 2020-03-05 15:54:14

I've talked about the  **Console API**  in one of  [my previous articles](https://areknawo.com/getting-creative-with-the-console-api/). However, there was one especially important detail that I didn't cover back then - the problem of  **logging objects**. So, what's the problem and how to solve it properly?

# What's the problem?
You've probably logged an object to the console before, right?

```javascript
const obj = { key: "value" };

console.log(obj);
```

Now, I don't want to remind you that everything in JS is an object. It's not important here. We're interested in properly logging an object, which here is just a "simple"  **key-value structure**.

Above you can see the most basic way to log an object - by using  `console.log()`. Don't get me wrong - it's completely fine to do it that way, but it has one fundamental flaw -  **dynamic evaluation**.

## Dynamic evaluation
When you get into your console window, you'll see your object logged nicely in a form of an  **expandable tree**. There will also be a little  **preview**  of what you can find inside.

![Example console.log() output](https://areknawo.com/content/images/2020/03/1.png)

But, while the preview itself shows the values (or rather a small fraction of them) from the moment of calling the  `console.log()`, the tree that you have to expand manually doesn't follow the same rule.

```javascript
const obj = { key: "value" };

console.log(obj);
setTimeout(() => {
  obj.key = "changed";
}, 2000);
```

With the snippet above, unless you manage to expand your logged object within 2 seconds, the value of  `key`  property in the console will be equal to  `"changed"`. That's due to the value being  **dynamically-evaluated**  at the moment of expanding the tree. However, from that point on, even if you change the value in your code, the logs will remain the same.

![Console.log() dynamic evaluation](https://areknawo.com/content/images/2020/03/2.png)

This whole thing might not be of a concert to you. But, if you work on code where the  **current state**  of the object's properties is important, you might want for your logs to be a bit more representative of the moment they were called in.

# Copying
The most obvious solution to such a problem would be to simply copy the logged object. Sure, it can take a bit of additional memory, but at the debugging phase, it's not that important.

Browsers implementing the  [ECMAScript 6 (ES6)](https://caniuse.com/#feat=mdn-javascript_builtins_object_assign)  standard, have a method called  `Object.assign()`  which is exactly what we need:

```javascript
// ...
console.log(Object.assign({}, obj));
```

`Object.assign()`  assigned all the properties of the passed objects to the first one and then returns it. This makes for an effective  **one-liner**, in which we copy all the properties (even from multiple objects) to a single target object, that's then displayed. In this way, we make sure that our logs won't be altered in the future.

Another, even better solution is the  **spread syntax**  (`...`) which has a bit worse  [cross-browser support](https://caniuse.com/#feat=mdn-javascript_operators_spread_spread_in_object_literals), but does essentially the same thing with less code to write:

```javascript
// ...
console.log({...obj});
```

Here, we expand/spread/copy properties from object  `obj`  to the new object literal the operator is used within.

## Deep copy
Now, if you work only with single-dimensional aka  _"flat"_  objects, you need to look no further - the solution above should satisfy all your needs.

However, because objects are copied by reference instead of value, neither spread syntax nor  `Object.assign()`  will work with  **nested objects**. Sure, top-most values will be fine, but all the properties of the nested objects will still be determined dynamically (i.e. after you expand them).

To solve this problem using a technique similar to what we've done a moment earlier, we'll need to use  **deep copying**. Basically, we have to go through all the properties and copy objects explicitly when needed.

Keep in mind that we also have to consider cases like  **circular references**  and other copied-by-reference values like arrays (depending on our needs). Thus, it's easier to simply use a  **utility library**  like  [Lodash](https://lodash.com/), instead of implementing the entire functionality on your own.

```javascript
// ...
console.log(_.cloneDeep(obj));
```

Here, we're using the  [`cloneDeep()`](https://lodash.com/docs/4.17.15#cloneDeep)  method from Lodash to deeply copy/clone the desired object.

Remember that if you don't want to  `import`  or even  `npm install`  the entire library, you can always use the method on its own through the extracted  [NPM package](https://www.npmjs.com/package/lodash.clonedeep).

# JSON
Copying an object is a great option when wanting to maintain the nice tree formatting and all that fancy stuff. But if all you need is some basic information,  `JSON.stringify()`  might be a good alternative.

```javascript
// ...
console.log(JSON.stringify(obj, null, 1));
```

You might not know that  `JSON.stringify()`  accepts  **2 optional arguments**. I've talked about this already in one of my  ["Tricks" articles](https://areknawo.com/7-adorable-web-development-tricks/). The first one is a  **replacer**  that can alter the processed values, while the second one is used as a number of  **spaces**  to insert within the created string. This way we end up with something like this:

![JSON.stringify() formatted output](https://areknawo.com/content/images/2020/03/3.png)

## Circular references
Now, while  `JSON.stringify()`  can deal with usual nested objects and even arrays just fine, it really struggles with circuital references, i.e.:

```javascript
const obj = {key: "value"};
obj.reference = obj;
```

There's an easy way to get around that - the replacer function. Take a look:

```javascript
// ...
const log = value => {
  const cache = [];

  console.log(JSON.stringify(value, (key, value) => {
	  if (typeof value === "object" && value !== null) {
	    if (cache.indexOf(value) !== -1) {
	      return "[[circular]]";
	    }
	    cache.push(value);
	  }
	  return value;
  }, 1));
};

log(obj);
```

What we've got here is essentially  `cloneDeep()`-like  **edge-case handling**, but for  `JSON.stringify()`. Instead of displaying the actual value, we show the  `"[[circular]]"`  string, to notify about the presence of a circular reference.

![Handling JSON.stringify() with circular references](https://areknawo.com/content/images/2020/03/4.png)

If you want, then with a bit of additional code, you could also implement the full support for displaying circular references.

Again, copying an object might be a better option most of the time, because of all the additional formatting and easy-to-use Lodash  `cloneDeep()`  method. But, ultimately, I think it's just a matter of  **preference**.

# Bottom line
It seems like even simple things like console logging can sometimes get quite  **complicated**. Well, I guess it's in the very nature of the programming profession. Anyway, I hope you find this article useful, and that it'll help you get even better at the art of  **debugging**!

If you like the post consider  **sharing it**  and  **following me**  on  [Twitter](https://twitter.com/areknawo) or [Facebook](http://facebook.com/areknawoblog). If you're interested, I also recommend checking out my  [YouTube channel](https://www.youtube.com/channel/UCUw6bg379ONlG1E_oxhwi-A/). Again, thanks for reading this piece and have a nice day!

<a href="https://www.buymeacoffee.com/areknawo" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/lato-orange.png" alt="Buy Me A Coffee" height="51px" width="217px"></a>