diff --git a/doc/api/util.md b/doc/api/util.md index 27bf540915cecb..ddd73d14b28cf2 100644 --- a/doc/api/util.md +++ b/doc/api/util.md @@ -167,6 +167,14 @@ corresponding argument. Supported placeholders are: * `%f` - Floating point value. * `%j` - JSON. Replaced with the string `'[Circular]'` if the argument contains circular references. +* `%o` - Object. A string representation of an object + with generic JavaScript object formatting. + Similar to `util.inspect()` with options `{ showHidden: true, depth: 4, showProxy: true }`. + This will show the full object including non-enumerable symbols and properties. +* `%O` - Object. A string representation of an object + with generic JavaScript object formatting. + Similar to `util.inspect()` without options. + This will show the full object not including non-enumerable symbols and properties. * `%%` - single percent sign (`'%'`). This does not consume an argument. If the placeholder does not have a corresponding argument, the placeholder is diff --git a/lib/util.js b/lib/util.js index 86be8612b97131..a9f267bd416ddc 100644 --- a/lib/util.js +++ b/lib/util.js @@ -137,6 +137,17 @@ function format(f) { str += f.slice(lastPos, i); str += String(arguments[a++]); break; + case 79: // 'O' + if (lastPos < i) + str += f.slice(lastPos, i); + str += inspect(arguments[a++]); + break; + case 111: // 'o' + if (lastPos < i) + str += f.slice(lastPos, i); + str += inspect(arguments[a++], + { showHidden: true, depth: 4, showProxy: true }); + break; case 37: // '%' if (lastPos < i) str += f.slice(lastPos, i); diff --git a/test/parallel/test-util-format.js b/test/parallel/test-util-format.js index 867cd57b62d543..27e3b62476211c 100644 --- a/test/parallel/test-util-format.js +++ b/test/parallel/test-util-format.js @@ -101,6 +101,84 @@ assert.strictEqual(util.format('%j', '42'), '"42"'); assert.strictEqual(util.format('%j %j', 42, 43), '42 43'); assert.strictEqual(util.format('%j %j', 42), '42 %j'); +// Object format specifier +const obj = { + foo: 'bar', + foobar: 1, + func: function() {} +}; +const nestedObj = { + foo: 'bar', + foobar: { + foo: 'bar', + func: function() {} + } +}; +assert.strictEqual(util.format('%o'), '%o'); +assert.strictEqual(util.format('%o', 42), '42'); +assert.strictEqual(util.format('%o', 'foo'), '\'foo\''); +assert.strictEqual( + util.format('%o', obj), + '{ foo: \'bar\',\n' + + ' foobar: 1,\n' + + ' func: \n' + + ' { [Function: func]\n' + + ' [length]: 0,\n' + + ' [name]: \'func\',\n' + + ' [prototype]: func { [constructor]: [Circular] } } }'); +assert.strictEqual( + util.format('%o', nestedObj), + '{ foo: \'bar\',\n' + + ' foobar: \n' + + ' { foo: \'bar\',\n' + + ' func: \n' + + ' { [Function: func]\n' + + ' [length]: 0,\n' + + ' [name]: \'func\',\n' + + ' [prototype]: func { [constructor]: [Circular] } } } }'); +assert.strictEqual( + util.format('%o %o', obj, obj), + '{ foo: \'bar\',\n' + + ' foobar: 1,\n' + + ' func: \n' + + ' { [Function: func]\n' + + ' [length]: 0,\n' + + ' [name]: \'func\',\n' + + ' [prototype]: func { [constructor]: [Circular] } } }' + + ' { foo: \'bar\',\n' + + ' foobar: 1,\n' + + ' func: \n' + + ' { [Function: func]\n' + + ' [length]: 0,\n' + + ' [name]: \'func\',\n' + + ' [prototype]: func { [constructor]: [Circular] } } }'); +assert.strictEqual( + util.format('%o %o', obj), + '{ foo: \'bar\',\n' + + ' foobar: 1,\n' + + ' func: \n' + + ' { [Function: func]\n' + + ' [length]: 0,\n' + + ' [name]: \'func\',\n' + + ' [prototype]: func { [constructor]: [Circular] } } } %o'); + +assert.strictEqual(util.format('%O'), '%O'); +assert.strictEqual(util.format('%O', 42), '42'); +assert.strictEqual(util.format('%O', 'foo'), '\'foo\''); +assert.strictEqual( + util.format('%O', obj), + '{ foo: \'bar\', foobar: 1, func: [Function: func] }'); +assert.strictEqual( + util.format('%O', nestedObj), + '{ foo: \'bar\', foobar: { foo: \'bar\', func: [Function: func] } }'); +assert.strictEqual( + util.format('%O %O', obj, obj), + '{ foo: \'bar\', foobar: 1, func: [Function: func] } ' + + '{ foo: \'bar\', foobar: 1, func: [Function: func] }'); +assert.strictEqual( + util.format('%O %O', obj), + '{ foo: \'bar\', foobar: 1, func: [Function: func] } %O'); + // Various format specifiers assert.strictEqual(util.format('%%s%s', 'foo'), '%sfoo'); assert.strictEqual(util.format('%s:%s'), '%s:%s'); @@ -125,6 +203,10 @@ assert.strictEqual(util.format('%f:%f'), '%f:%f'); assert.strictEqual(util.format('o: %j, a: %j', {}, []), 'o: {}, a: []'); assert.strictEqual(util.format('o: %j, a: %j', {}), 'o: {}, a: %j'); assert.strictEqual(util.format('o: %j, a: %j'), 'o: %j, a: %j'); +assert.strictEqual(util.format('o: %o, a: %O', {}, []), 'o: {}, a: []'); +assert.strictEqual(util.format('o: %o, a: %o', {}), 'o: {}, a: %o'); +assert.strictEqual(util.format('o: %O, a: %O'), 'o: %O, a: %O'); + // Invalid format specifiers assert.strictEqual(util.format('a% b', 'x'), 'a% b x');