5

Minor note: I'm very familiar with console.log(), JSON.stringify(), Object.prototype.toString(), util.inspect() etc - this question isn't asking how to show the contents of objects, but rather why node's behavior changes in different circumstances.

I know I can console.log(someObject) and node will print:

[object Object]

node js function return [object Object] instead of a string value has some good information on this.

I know that [object Object] is from Object.prototype.toString()

I know I can console.log(JSON.stringify(someObject, null, 2) and node will print:

{
  foo: 'bar'
}

Or use util.inspect() etc. See this answer

However it seems that sometimes node will actually print the objects contents

If I make a new file called runme.js with the contents:

console.log({foo: 'bar'})

And run node runme.js node will print

{ foo: 'bar' }

Not [object Object]

Why is node not printing [object Object]?

Edit: per Keith's question, [object Object] will appear when I run:

console.log(`Check me out ${{foo: 'bar'}}`)

Logs [object Object]

What determines whether node uses Object.prototype.toString() (aka [object Object]) vs printing the contents of the object?

mikemaccana
  • 110,530
  • 99
  • 389
  • 494
  • 1
    Have you an example of someObject that does this, as that might help. eg. `{foo: 'bar'}` would work fine anyway without using stringify.. – Keith Oct 27 '20 at 10:49
  • 1
    Does the object implement [the `nodejs.util.inspect.custom` symbol method](https://nodejs.org/api/util.html#util_util_inspect_custom)? – VLAZ Oct 27 '20 at 10:57
  • @Keith added an example. Per the answers, looks like the ES6 string literal was running the `toString()` before the `console.log` could do anything, – mikemaccana Oct 27 '20 at 11:12
  • @VLAZ no it doesn't. Good question though! – mikemaccana Oct 27 '20 at 11:14
  • Yes, that makes sense why it's [object Object], in theory though you could create your own template literal parser to show it differently.. – Keith Oct 27 '20 at 11:17

3 Answers3

4

Console logging will output [object Object] for an object value that was converted to a string before being passed as a parameter to a console method, or if an object value argument is consumed and inserted into a format string passed as the first argument.

The latter occurs if a format string contains C-like percentage prefixed conversion sequences within it that consume the next unused argument and embed it the format string.

If a format string is used, and more (object) arguments are passed to the method than are used by the format string, remaining unconsumed arguments that are objects are logged as object values without conversion to strings.

So some expected values and actual log output:

const someObject = {a: "a"};

// expected output: "string [object Object]"
console.log( "string " + someObject);

// expected output: "same deal: [object Object]"
console.log( "same deal: %s", someObject); // converted by format string

// expected output: 'someObject = {a: "a"}'
console.log( "someObject = ", someObject) // someObject was not consumed by format string!

Of course Node does not present an interactive object view on the text console, and in simple cases it can look a lot like JSON.stringify() output. However Node does make an attempt to

  • identify the constructor of the object,
  • color code values according to their data type
  • handle presentation of circular references

that expand its debugging potential beyond simple JSON.stringify() conversions.

mikemaccana
  • 110,530
  • 99
  • 389
  • 494
traktor
  • 17,588
  • 4
  • 32
  • 53
  • 1
    Thanks @traktor53! I made a minor edit as `aa = { a: 'a' }` was a bit hard to read but other than that this was perfect. – mikemaccana Oct 27 '20 at 11:10
  • 1
    This is exactly what was happening too - see the edit in the question. An ES6 string literal was running `toString()` before `console.log()` could do it's work. – mikemaccana Oct 27 '20 at 11:14
1

In my experience, console.log detects when it is passed the object and prints the entire object like the example you showed.

console.log({foo: 'bar'})
// => { foo: 'bar' } 

However when an object is concatenated to a string, it's converted to a string using the .toString() method and then passed to the console.log method.

Example:

console.log('' + { foo: 'bar' }) 
// => [object Object]

This is because before passing to console.log method, the expression is evaluated to '' + { foo: 'bar' }.toString(). That's my understanding of how it works. Though the internal workings might be more complex.

tbking
  • 8,796
  • 2
  • 20
  • 33
1

As has been worked out, template literals will box the args using .toString.

But if you find you use template literals a lot for debugging, and you want to use JSON.stringify instead.

Here is a simple example below.

function tagJSON(strings, ...args) {
  const output = [];
  let nextString = 0;
  let nextArg = 0;
  //loop all string & args
  while (nextArg < args.length) {
    output.push( strings[nextString] );
    output.push( JSON.stringify(args[nextArg]) );
    nextArg += 1;
    nextString += 1;
  } 
  //might be a string left at the end..
  if (nextString < strings.length)
    output.push( strings[nextString] ); 
  //finally output our new concated string & stringified(args)
  return output.join('');
}

var someobject = {x: 'y'};
let output = tagJSON`someobject = ${someobject}`;

console.log(output);
console.log(tagJSON`boolean = ${true} or ${false}`);
console.log(tagJSON`strings will double quote ${'hello world'}`);
Keith
  • 22,005
  • 2
  • 27
  • 44