toString() and Symbol.toStringTag

The default Object.prototype.toString() function is defined to the specification to return [object X] even for non-objects:

Object.prototype.toString.call(undefined) // returns "[object Undefined]"
Object.prototype.toString.call(true) // returns "[object Boolean]"
{}.toString() // returns "[object Object]"

X is a so-called ‘string tag’ and in newer versions of Javascipt, you can set it yourself:

{[Symbol.toStringTag]:"MyObject"}.toString() // returns "[object MyObject]"

But for objects, you can also just implement the complete toString function:

{toString: ()=> "My Cool Object!"}.toString() // returns "My Cool Object!"

However, the toString() function is not used by console.log() or in the Node.js or Deno REPLs.

inspect() is the real toString()

In Node.js, objects are converted to strings using the util.inspect() function. In Deno, there is the equivalent Deno.inspect(). These methods are used both in the REPLs and when using console.log(). You can influence what these do by implementing a function with the symbol nodejs.util.inspect.custom and Deno.customInspect respectively. If you implement both, pretty printing works in both. Check out this example for a binary tree:

export class Tree {
    constructor(value, left, right) {
        this.value = value;
        this.left = left;
        this.right = right;
    }

    [Symbol.for("Deno.customInspect")]() {
        return this.toString();
    }
    [Symbol.for("nodejs.util.inspect.custom")](){
        return this.toString();
    }
    toString() {
        const printNode = (node, prefix = '', isLeft) => {
            let str = '';
            if (node !== null) {
                str += `${prefix}${isLeft ? '├──' : '└──'} ${node.value}\n`;
                const newPrefix = `${prefix}${isLeft ? '│   ' : '    '}`;
                str += printNode(node.left, newPrefix, true);
                str += printNode(node.right, newPrefix, false);
            }
            return str;
        };
        return printNode(this).trim();
    }
}

And here is a Deno session that uses it:

> tree
└── 52
    ├── 41
    │   ├── 51
    │   └── 44
    └── 94
        ├── 54
        └── 36
> console.log(tree);
└── 52
    ├── 41
    │   ├── 51
    │   └── 44
    └── 94
        ├── 54
        └── 36

Isn’t that beautiful? Sadly there is no way to accomplish this in browsers.