I just wrote this simple function in order to implement something like console.table
:
function toTable(obj) {
let arr = '';
arr += '--------------------------\r\n';
for (let [key, value] of Object.entries(obj[0])) {
arr += ' ' + key + '\t';
}
for (let i = 0; i < obj.length; i++) {
arr += '\r\n--------------------------\r\n';
for (let [key, value] of Object.entries(obj[i])) {
arr += ' ' + value + '\t\t';
}
}
arr += '\r\n--------------------------';
return arr;
}
An example with an array of objects:
let arr = [
{ firstName: 'Sirwan', lastName: 'Afifi', age: 27 },
{ firstName: 'Person2', lastName: 'P2', age: 32 }
];
It gives you following result:
--------------------------
firstName lastName age
--------------------------
Sirwan Afifi 27
--------------------------
Person2 P2 32
--------------------------
Any suggestions?
-
\$\begingroup\$ I would add a condition to check for empty arrays, a fairly common condition. Another common use case is arrays of arrays. \$\endgroup\$Marc Rohloff– Marc Rohloff2017年03月10日 18:13:04 +00:00Commented Mar 10, 2017 at 18:13
2 Answers 2
Here are some things that can be improved
Naming:
toTable
is expressive but also confusing. It looks like it is converting something to table. When used in conjunction with HTML this could be confused with<table>
. Adding comments to the function can be helpful to understand what the function is doing. But again, if comments are needed that means you're not naming variables correctly.obj
the short of object is descriptive when used for naming object. However, for this function it is wrong as the function is expecting an array.arr
orarray
would be better.arr
Again, the variable is used to store a string.str
can be used or some developers even usestr
as prefix e.g.strTable
,strLine
.
Repetitive Code:
'--------------------------\r\n'
is used three times in the code. You can create a variable to store this string value and use it anywhere to add the line of hyphens.
for
loops:
for
loops is used three times in the code. Nested loops should be avoided. There are very few cases when nested loops are required. Always try to avoid nested loops wherever possible.
In the first loop, the keys of the object are joined using tab character.
for (let [key, value] of Object.entries(obj[0])) {
arr += ' ' + key + '\t';
}
This can be done using Object.keys()
which returns an array of keys. Then Array#join
can be used on keys array to join the array elements by glue-\t
in this case.
So, the new code will be
Object.keys(arr[0]).join('\t') // That's it!
The other nested loop will be slow if object is having large number of properties.
for (let i = 0; i < obj.length; i++) {
arr += '\r\n--------------------------\r\n';
for (let [key, value] of Object.entries(obj[i])) {
arr += ' ' + value + '\t\t';
}
}
Looking at it carefully shows that this only create a string of values separated by tab. Object.values
can be used for this. But again, as the objects are inside array, it need to be iterated over and then values can be extracted from object. New code using this function will look like
arr.forEach(function(obj) {
someVar += line + Object.values(obj).join('\t\t'); // Sweet!
});
Apart from these, below are some suggestions:
Using
console.table
consistently: Ifconsole.table
is supported by target environment then why not use the beautiful function? Although, this function(toTable()
from question) is not complete as it doesn't support printing object in table, object containing different keys, nested object, etc. For the basic implementation, it can override default method.if (!console.table) { // Use fallback only when not available console.table = function(arr) { // Code here }; }
Creating the horizontal line dynamically.
The horizontal line is static. In case when the object contain less/more properties, it'll not fit correctly. To create the line dynamically again,
Object.keys()
can be used.'-'.repeat(8 * keys.length) // Assuming a tab(\t) is eight characters long
Using monospace font to log data
For non-monospace fonts not all characters occupy same space. For example,
i
takes less space thanO
.console.log
can be set with custom font using the second parameter which is CSS styles.console.log('%c' + someString, 'font-family: monospace');
Here's code with these changes
if (!console.table) {
console.table = function(arr) {
'use strict';
let keys = Object.keys(arr[0]);
const horizontalLine = '\r\n' + '-'.repeat(8 * keys.length) + '\r\n';
let heading = Object.keys(arr[0]).join('\t');
let str = horizontalLine;
str += heading;
arr.forEach(function(obj) {
str += horizontalLine + Object.values(obj).join('\t\t');
});
str += horizontalLine;
console.log('%c' + str, 'font-family: monospace');
};
}
let arr = [
{ firstName: 'Sirwan', lastName: 'Afifi', age: 27 },
{ firstName: 'Person2', lastName: 'P2', age: 32 }
];
console.table(arr);
Here's fiddle to try out it.
And here's output of the above code
------------------------
firstName lastName age
------------------------
Sirwan Afifi 27
------------------------
Person2 P2 32
------------------------
Note:
- As some of the features are only available in latest browsers/nodeJS version and some are experimental, see the MDN link embedded for browser support and polyfill.
- This, by any means is not polyfill for
console.table
. This is just another version of the OP's code.console.table
is overriden with assumptions that only the provided input format is used to log in table. - The horizontal lines are not perfect. They can contain less/more depending on the min and max length of key in the object.
-
1\$\begingroup\$ Cool, I learned stuff. Too bad that your excellent answer is now good competition with my answer ;) \$\endgroup\$konijn– konijn2017年03月10日 15:51:26 +00:00Commented Mar 10, 2017 at 15:51
Interesting question;
- The variable name
arr
is unfortunate, you are not dealing with an array but with a string - You are copy pasting
'--------------------------\r\n'
, you should either have a constant for this, or, dynamically calculate how long it should be so that the output is always beautiful for (let [key, value] of Object.entries(obj[0])) {
, why go overvalue
if you don't use it?- Also, consider
Array.join()
which is meant for this kind of situations This
let arr = ''; arr += '--------------------------\r\n';
should be
let arr = '--------------------------\r\n';
and in fact, that
\r
looks iffy, unless you have a very specific use case, I would uselet arr = '--------------------------\n';
- It does not make much sense to separate keys with
\t
and values with\t\t
, why do you do this? - Same as my 2nd point, why go over
key
in a loop where you don't usekey
?
This is my first counterproposal:
function arrayToString( array ){
return ' ' + array.join(' \t');
}
function toTable2(objectArray) {
const line = '--------------------------';
let arr = [line, arrayToString( Object.keys(objectArray[0]) )];
for (let i = 0; i < objectArray.length; i++) {
arr.push(line);
arr.push( ' ' + arrayToString( Object.values( objectArray[i] ) ) );
}
arr.push(line);
return arr.join('\n');
}
The output is slightly different, because this outputs keys in the same manner as values. This shows the limits of just placing tabs, if you want a visually pleasing output, then you need the check the length of each column and pad spaces.
You could go a little bit more functional, by realizing that you are parsing every element in a list, so you could use map
:
function toTable3(objectArray) {
const line = '--------------------------\n';
let list = [Object.keys(objectArray[0])].concat( objectArray.map( o=>Object.values(o) ) );
list = list.map( list => arrayToString(list) );
return line + list.join('\n'+line) + '\n' + line;
}
This is my last counter-proposal, it is a tad too dense for me, but it shows some more of the power in map
and join
. If this were real production with expected maintenance of the code, then I would spread this out over more lines of code:
function toTable4(objectArray){
//Extract keys
const keys = Object.keys( objectArray[0] );
//Build a list with keys and values
let list = [keys].concat( objectArray.map( o=>Object.values(o) ) );
//For each key, get the maximum value or key length, add 1 to space out
const lengths = keys.map( (key, index) => Math.max.apply(null, list.map( o => (o[index]+'').length ) ) + 1 );
//Build a line of the right amount of '-'
const line = '-'.repeat( lengths.reduce( (out,value)=>out+value , 0 ) + 1 )
//Convert every object to an evenly spaced line with a lines separator
list = list.map( o => ' ' + o.map( ( value, index) => value + ' '.repeat( lengths[index] - value.length ) ).join('') + '\n' + line );
//Return each line separated with a newline, prefixed with one more line
return line + '\n' + list.join('\n');
}