Pretty print an object - JavaScript I've written a function to prettify a json object, as if it were using JSON.stringify.

I'm only focused on strings and other objects, nothing else.

Is there a better way of doing this? perhaps using a data structure?



const exampleJson = {"name":"Jon","facts":{"car":"Ford","address":{"city":"New York"},"watch":"Casio","other": {}}};

function prettyPrint(data) {
  let tabbify = n =&gt; Array(n).fill(' ').join('');

  let traverse = (data, tab = 1) =&gt; {
    let output = "",
        open   = `{\n`,
        close  = `\n${tabbify(tab - 1)}}`;

    output += open;
    
    Object.entries(data).forEach(([key, val], i, {length}) =&gt; {
      if (typeof val === 'string') {
        output += `${tabbify(tab)} "${key}": "${val}"`;
      } else if (typeof val === 'object') {
        if (Object.keys(val).length &gt; 0) {
          output += `,\n${tabbify(tab)} "${key}": ${traverse(val, tab+2)}${i === length - 1 ? '' : ',\n'}`;
        } else {
          output += `,\n${tabbify(tab)} "${key}": {}`;
        }
      }
    })

    output += close;
    return output;
  }

  return traverse(data);
}




Output

{
    "name": "Jon",
    "facts": {
        "car": "Ford",
        "address": {
            "city": "New York"
        },
        "watch": "Casio",
        "other": {}
    }
}
I am prejudiced against variables named data.

You need to escape literal ", \, and \n characters in quoted strings.  It's good form to do tabs too.

Using a dispatch table for types (instead of if/else) makes it easy to handle all the types.

Instead of passing tab up and down the call stack, wrap the calls in a function that indents a block of text.  Nested traverse will be nested in indent too and get indented the right number of times.  

Instead of checking whether you're at the end of a delimited list (to decide whether to add a delimiter), use arr.join(delimiter). It puts delimiters on inner boundaries only.

Array(n).fill(' ').join(''); is the same as ' '.repeat(n).

It would be nice to omit the quotes on property names that don't need them.  Doing this perfectly happens to be really laborious&mdash;the regex is 11 kilobytes long!!  It's manageable if we limit the exceptions to 7-bit ASCII, which is probably a good idea anyway.



function prettyPrint(obj) {
    const stringify = {
        "undefined": x =&gt; "undefined",
        "boolean":   x =&gt; x.toString(),
        "number":    x =&gt; x,
        "string":    x =&gt; enquote(x),
        "object":    x =&gt; traverse(x),
        "function":  x =&gt; x.toString(),
        "symbol":    x =&gt; x.toString()
    },
    indent = s =&gt; s.replace(/^/mg, "  "),
    keywords = `do if in for let new try var case else enum eval null this true 
            void with await break catch class const false super throw while 
            yield delete export import public return static switch typeof 
            default extends finally package private continue debugger 
            function arguments interface protected implements instanceof`
       .split(/\s+/)
       .reduce( (all, kw) =&gt; (all[kw]=true) &amp;&amp; all, {} ),
    keyify = s =&gt; ( !(s in keywords) &amp;&amp; /^[$A-Z_a-z][$\w]*$/.test(s) ? s : enquote(s) ) + ": ",
    enquote = s =&gt; s.replace(/([\\"])/g, '\\$1').replace(/\n/g,"\\n").replace(/\t/g,"\\t").replace(/^|$/g,'"'),
    traverse = obj =&gt;  [ 
           `{`,
            indent( Object.keys(obj) 
                    .map( k =&gt; indent( keyify(k) + stringify[ typeof obj[k] ](obj[k]) ) )
                    .join(",\n")
                    ),
            `}`
        ]
        .filter( s =&gt; /\S/.test(s) )
        .join("\n")
        .replace(/^{\s*\}$/,"{}");
    return traverse(obj);
}

console.log(prettyPrint( 
    {
        "name":"Jon",
        "facts":{
            "car":"Ford",
            "address":{
               "city":"New York"
            },
            "watch":"Casio",
            "other": { 
                "true":false, 
                blargh:undefined, 
                "111number":1e5, 
                method:function(x){x++} 
            }
        }
    } 
));