Why I implemented my own JSON.stringify() and console API

Cookie Engineer
6 min readJul 25, 2018

This time, I am writing an article that could be interpreted as a nerdraging one. But actually, I’m just trying to fix stuff so that I can focus on work instead; without getting annoyed all the time that debugging is so hard.

TL;DR

node.js console API is shitty, JSON.stringify() sucks, this is the polyfill for a better developer experience: https://gist.github.com/cookiengineer/05581bc59f5d7d3dbaa09e6018678740

Why

In node.js, the console API is pretty much the definition of being utterly useless. Every time you want to console.log(object) and the object is, say, of nested nature, you can’t see shit about what’s going on. Not only that, the standard (native) console API also totally does not print out functions when an object has one.

Every time I am debugging whether or not the current object actually has methods, I have to manually do something like this, just to be able to see what’s going on:

let keys = Object.keys(object);keys.forEach(key => {
console.log(key, typeof object[key]);
});

To be honest, this is a huge time killer for me. Debugging console.log() statements in a server-side log, maybe even connected via ssh, is making me smash my keyboard into the monitor.

Step 1: Formulating the Problem

Okay, so I started to reflect on the underlying problem: Why do I get pissed? What’s the problem? What’s the inefficiency? How would my ideal console API look like?

I started to mock up some ideas I had in mind, and took some notes over a couple hours on what particular stuff I was dumping in the console and why I had to rework my code, and re-execute the program(s) later. Turns out most of my use cases that were pretty simple:

  • console.log(long_array) leads to shitty displays, and I need to scroll up and down and actually log it in chunks, so that I have less scroll space used
  • console.log(matrix) makes my head hurt, because I usually have matrices in a one-dimensional array and I have no idea which index I’m looking at in the console dump
  • console.log(nested_object) usually is reduced in the second hierarchy, I’d prefer something similar to JSON.stringify(), but for ALL data types, not hiding any properties on the objects
  • console.log(object) is shitty when I want to debug instances of functions (instances of classes, composites, or whatever). Mostly because properties with their type being a function is unreadable, even when using a JSON.stringify replacer is making situation worse as it will lead to \\n all over the place. Impossible to desanitize later.

Implementing a better console API for node.js

So I like things minimal, I like things without dependencies and I like things shiny and sparkly in my Terminal. We have 2018, so a colored console can be assumed pretty much anywhere. If not, then the user should write perl code instead and just get on with his dying craftsmanship.

Color Codes

So I decided I want color codes in the shell when using the console API in order to identify logs that have different types more efficiently. Emojis usually fail via SSH (I guess UTF8 is still messed up everywhere), so for the time being I just stay with good ol’ color codes:

  • console.info() should be highlighted in green
  • console.warn() should be highlighted in yellow
  • console.error() should be highlighted in red
  • console.log() should stay uncolored
  • console.clear() should clear the console, so that the screen usage is more efficient when being used via, say, ssh or similar network tunnels

As not all shells and usages later support color codes, there should be an identifier as the first character per-statement that identifies the type. This would be something like (L) for console.log() calls, and (I) , (W) or (E) for the other log levels.

Data Types

Usually when debugging stuff in the console, some data types are totally messed up. For example console.log(new Date()) will be pretty much random on how it will be printed, and when working with timestamps and optimizing large code bases, you have no idea what you’re looking at because shit gets complex with many states real fast.

Also, arrays that contain primitives should be rendered differently than arrays containing, say, nested objects. If it contains only primitives, it usually would be better to just render it in a single line (given it’s no matrix), as the values and cells are much easier to both count and identify then.

If an array is a matrix, and matches the criteria of being power-of-two it can be rendered in a squared form. Also, usually matrixes contain many many values, and when the commas of the cells are not arranged, it is very very hard to debug what’s going on. So this auto-alignment feature is key to being more efficient when looking at those matrix arrays.

Primitive data types should behave like JSON, but extend it so that Infinity, -Infinity , NaN and undefined are also printed correctly.

Step 2: Trying to use JSON.stringify()

When trying to implement a little helper method, most solutions on the internet (aka stackoverflow) suggest using a custom replace for JSON.stringify() — but I heavily disrecommend it for the following reasons:

If you use JSON.stringify() with properties that would be rendered in multiple lines (e.g. a large array, a function body or nested objects), you cannot implement it using the replacer callback, as every string returned will be mapped with \\n in the end and are pretty much impossible to sanitize later.

let object = {
foo: function() {
return 'something';
}
};
console.log(object); // Look ma, it's gone!let blob = JSON.stringify(object, '\t', function(key, val) {
if (typeof val === 'function') {
return (val).toString();
} else {
return val;
}
});
console.log(blob); // will have a single line containing the function body, but with a value similar to "function(){\\n..."

So yeah, we’ve pretty much ruled out JSON.stringify() with above problem already. It gets more complex when you want to render different types of arrays in a different manner, but we’ll ignore that for now.

Let’s just assume that I spent several hours trying to desanitize the strings correctly later, and I finally gave up because there’s no real solution to it that won’t fuck up function bodies that have, say something like return '\n'; in them.

Step 3: Implementing a custom console API

Now that we’ve cleared up what we cannot use, it’s time to describe on what we can actually use to implement above described featureset. In order to render things correctly, we will have to implement the following featureset in a custom manner:

  • _stringify(data, indent) to have a similar behaviour as JSON.stringify(data, '\t') would have
  • _args_to_string(args, offset) so that we can use multiple arguments in the console.log(a, b, c) statements and an offsetthat allows us to indent things based on the process.stdout.columns so that the full terminal width can be used

It’s probably a bit too complicated to dig into the code more in detail, but the polyfill for the better console API is — for the sake of simplicity — put into a gist over here on github: https://gist.github.com/cookiengineer/05581bc59f5d7d3dbaa09e6018678740

Step 4: The final result

So when using a demo code like this:

#!/usr/bin/env noderequire('./console-polyfill.js');let data1 = {
foo: {
bar: {
qux: [
1,
2.3,
true,
false,
Infinity,
-Infinity
]
}
}
};
let data2 = {
time: new Date(),
method: function(a, b, c) {
// Look ma, it's correctly indented!
if (a === b) {
return c + 1;
}
return a + b;},
matrix: [
1, 2, 3, 4,
5, 6, 7, 8,
9, 10, 11, 12,
13, 14, 15, 16
]
};
console.log('Better console API demo');
console.info(new Date());
console.log(data1);console.warn(data2.time);
console.log(data2);
console.info(new Date());

… it will lead to this pretty and shiny output:

Better console API for node.js

--

--

Cookie Engineer

Mad Scientist builds a self-improving AI learning how to automate itself. Backpropagated ES/HyperNEAT lover. https://lychee.js.org http://artificial.engineering