JavaScript data formatting/pretty printer
Asked Answered
S

16

131

I'm trying to find a way to "pretty print" a JavaScript data structure in a human-readable form for debugging.

I have a rather big and complicated data structure being stored in JS and I need to write some code to manipulate it. In order to work out what I'm doing and where I'm going wrong, what I really need is to be able to see the data structure in its entirety, and update it whenever I make changes through the UI.

All of this stuff I can handle myself, apart from finding a nice way to dump a JavaScript data structure to a human-readable string. JSON would do, but it really needs to be nicely formatted and indented. I'd usually use Firebug's excellent DOM dumping stuff for this, but I really need to be able to see the entire structure at once, which doesn't seem to be possible in Firebug.

Spectrogram answered 24/9, 2008 at 22:46 Comment(4)
Not sure if you are notified of edits of answers. So I write this comment to inform you that I added my own version of indented dump. :-)Matlick
Note: The JSON.stringify() answer seems to be quite useful, though it's not accepted as 'the' answer.Concepcionconcept
You could get a visual and intuitive output of objects using nodedump: github.com/ragamufin/nodedumpDehiscence
Take a look there: #4811341Gratuity
M
33

I wrote a function to dump a JS object in a readable form, although the output isn't indented, but it shouldn't be too hard to add that: I made this function from one I made for Lua (which is much more complex) which handled this indentation issue.

Here is the "simple" version:

function DumpObject(obj)
{
  var od = new Object;
  var result = "";
  var len = 0;

  for (var property in obj)
  {
    var value = obj[property];
    if (typeof value == 'string')
      value = "'" + value + "'";
    else if (typeof value == 'object')
    {
      if (value instanceof Array)
      {
        value = "[ " + value + " ]";
      }
      else
      {
        var ood = DumpObject(value);
        value = "{ " + ood.dump + " }";
      }
    }
    result += "'" + property + "' : " + value + ", ";
    len++;
  }
  od.dump = result.replace(/, $/, "");
  od.len = len;

  return od;
}

I will look at improving it a bit.
Note 1: To use it, do od = DumpObject(something) and use od.dump. Convoluted because I wanted the len value too (number of items) for another purpose. It is trivial to make the function return only the string.
Note 2: it doesn't handle loops in references.

EDIT

I made the indented version.

function DumpObjectIndented(obj, indent)
{
  var result = "";
  if (indent == null) indent = "";

  for (var property in obj)
  {
    var value = obj[property];
    if (typeof value == 'string')
      value = "'" + value + "'";
    else if (typeof value == 'object')
    {
      if (value instanceof Array)
      {
        // Just let JS convert the Array to a string!
        value = "[ " + value + " ]";
      }
      else
      {
        // Recursive dump
        // (replace "  " by "\t" or something else if you prefer)
        var od = DumpObjectIndented(value, indent + "  ");
        // If you like { on the same line as the key
        //value = "{\n" + od + "\n" + indent + "}";
        // If you prefer { and } to be aligned
        value = "\n" + indent + "{\n" + od + "\n" + indent + "}";
      }
    }
    result += indent + "'" + property + "' : " + value + ",\n";
  }
  return result.replace(/,\n$/, "");
}

Choose your indentation on the line with the recursive call, and you brace style by switching the commented line after this one.

... I see you whipped up your own version, which is good. Visitors will have a choice.

Matlick answered 24/9, 2008 at 23:6 Comment(9)
I like ;) Can't get it to work properly, but if you don't mind, I'm going to shamelessly steal the concept and write my own :)Spectrogram
One short coming of this approach (compared with the JSON.stringify method Jason suggests) is that it does not show arrays of objects properly. When you have an array of objects it shows up as [object Object].Hellenic
@Ryan: You mean browser's native objects? Yes, looking back at my code, I saw I added a comment: // Too bad if one field is an object... :-P OK for my test here ... It is OK to dump user made structures. I see there are alternatives below, if you need something more robust.Matlick
I cannot use this. I get infinite loop when I try to dump some json data.Robbi
There is some kind of loop in that function, I couldn't find where. Gives Uncaught RangeError: Maximum call stack size exceededHammett
@Hammett It is a recursive function, it calls itself. If you use it with a huge object, indeed it can exceed the call stack size, although it is unlikely. Or, like neoneye, you might have a reference doing a loop in the object (obj a references obj b referencing back obj a), which kills such algorithm. The Lua version detected such case, at the cost of added complexity.Matlick
@Matlick Thanks for the answer. Luckily I found an amazing var dumper, check it out: github.com/padolsey/prettyPrint.js Make a table with all stuff, configurable depth and etc. Tested on my object which had the stack siz reached and worked fine.Hammett
Yes, mine is very simplistic, easy to include in a small project for fast results, but limited. There are more robust dumpers around, some listed in this page...Matlick
@Hammett & PhiLho - The Maximum call stack size could also be triggered on a small object; one with a property reference to itself. Such a reference would cause an infinite loop with this function.Ranite
C
242

Use Crockford's JSON.stringify like this:

var myArray = ['e', {pluribus: 'unum'}];
var text = JSON.stringify(myArray, null, '\t'); //you can specify a number instead of '\t' and that many spaces will be used for indentation...

Variable text would look like this:

[
  "e",
   {
      "pluribus": "unum"
   }
]

By the way, this requires nothing more than that JS file - it will work with any library, etc.

Canst answered 24/9, 2008 at 23:1 Comment(11)
This is almost definitely the best answer you're going to get. I have taught 4 or 5 non-programmers to read and edit JSON.stringified data structures and use them extensively for configuration files.Playgoer
Yes, very good answer, unfortunately, it doesn't seem to be playing nicely with something else in this app (not sure what yet). Works in isolation, though.Spectrogram
Weird that it would cause problems - it does introduce the name "JSON" to the global namespace, so that may cause you problems. Check your namespace for "JSON" before adding this to see if a collision exists.Canst
Yeah checked that. It's probably down to the prototype library, as it adds toJSON() methods to lots of stuff (not Object, but most everything else), which looks like it'll cause trouble.Spectrogram
Also, maybe you should use the alternative of providing a number instead of '\t' as the last param...let us know what you find out.Canst
It is indeed clashing with prototype's toJSON(), which returns a json string, which Doug's stuff is then jsonifying. Which, uh, isn't right :)Spectrogram
Well, prototype is evil like that... ;)Canst
An update on this, with Firefox 3.5 and above, JSON.stringify is built-in. (developer.mozilla.org/En/Using_JSON_in_Firefox), so if you are just trying to see a JSON object for debugging purposes, you can do it with no extra JS dependencies.Katey
Also in Chrome. However, JSON.stringify fails on circular data JSON.stringify((function(){var x = []; x.push(x); return x})()) and on many other kinds of objects JSON.stringify(/foo/).Kyles
Using this code snippet works in Firefox's FireBug console i.e. console.log('myObj', JSON.stringify(myObj,null,'\t')); However when I try to copy the console output to clipboard I get back the single line output. Firebug console however shows the indented output from JSON.stringify(). Any ideas why this happens?Concepcionconcept
Firebug seems to wrap the output in a <span>. So to copy the indented text I need to a) first save the console output as HTML, b) View the HTML source and c)Copy the indented part from HTML source's <span> element to clipboard. Phew!! Quite a lot of work just to copy the indented data-structure it would seem.Concepcionconcept
M
33

I wrote a function to dump a JS object in a readable form, although the output isn't indented, but it shouldn't be too hard to add that: I made this function from one I made for Lua (which is much more complex) which handled this indentation issue.

Here is the "simple" version:

function DumpObject(obj)
{
  var od = new Object;
  var result = "";
  var len = 0;

  for (var property in obj)
  {
    var value = obj[property];
    if (typeof value == 'string')
      value = "'" + value + "'";
    else if (typeof value == 'object')
    {
      if (value instanceof Array)
      {
        value = "[ " + value + " ]";
      }
      else
      {
        var ood = DumpObject(value);
        value = "{ " + ood.dump + " }";
      }
    }
    result += "'" + property + "' : " + value + ", ";
    len++;
  }
  od.dump = result.replace(/, $/, "");
  od.len = len;

  return od;
}

I will look at improving it a bit.
Note 1: To use it, do od = DumpObject(something) and use od.dump. Convoluted because I wanted the len value too (number of items) for another purpose. It is trivial to make the function return only the string.
Note 2: it doesn't handle loops in references.

EDIT

I made the indented version.

function DumpObjectIndented(obj, indent)
{
  var result = "";
  if (indent == null) indent = "";

  for (var property in obj)
  {
    var value = obj[property];
    if (typeof value == 'string')
      value = "'" + value + "'";
    else if (typeof value == 'object')
    {
      if (value instanceof Array)
      {
        // Just let JS convert the Array to a string!
        value = "[ " + value + " ]";
      }
      else
      {
        // Recursive dump
        // (replace "  " by "\t" or something else if you prefer)
        var od = DumpObjectIndented(value, indent + "  ");
        // If you like { on the same line as the key
        //value = "{\n" + od + "\n" + indent + "}";
        // If you prefer { and } to be aligned
        value = "\n" + indent + "{\n" + od + "\n" + indent + "}";
      }
    }
    result += indent + "'" + property + "' : " + value + ",\n";
  }
  return result.replace(/,\n$/, "");
}

Choose your indentation on the line with the recursive call, and you brace style by switching the commented line after this one.

... I see you whipped up your own version, which is good. Visitors will have a choice.

Matlick answered 24/9, 2008 at 23:6 Comment(9)
I like ;) Can't get it to work properly, but if you don't mind, I'm going to shamelessly steal the concept and write my own :)Spectrogram
One short coming of this approach (compared with the JSON.stringify method Jason suggests) is that it does not show arrays of objects properly. When you have an array of objects it shows up as [object Object].Hellenic
@Ryan: You mean browser's native objects? Yes, looking back at my code, I saw I added a comment: // Too bad if one field is an object... :-P OK for my test here ... It is OK to dump user made structures. I see there are alternatives below, if you need something more robust.Matlick
I cannot use this. I get infinite loop when I try to dump some json data.Robbi
There is some kind of loop in that function, I couldn't find where. Gives Uncaught RangeError: Maximum call stack size exceededHammett
@Hammett It is a recursive function, it calls itself. If you use it with a huge object, indeed it can exceed the call stack size, although it is unlikely. Or, like neoneye, you might have a reference doing a loop in the object (obj a references obj b referencing back obj a), which kills such algorithm. The Lua version detected such case, at the cost of added complexity.Matlick
@Matlick Thanks for the answer. Luckily I found an amazing var dumper, check it out: github.com/padolsey/prettyPrint.js Make a table with all stuff, configurable depth and etc. Tested on my object which had the stack siz reached and worked fine.Hammett
Yes, mine is very simplistic, easy to include in a small project for fast results, but limited. There are more robust dumpers around, some listed in this page...Matlick
@Hammett & PhiLho - The Maximum call stack size could also be triggered on a small object; one with a property reference to itself. Such a reference would cause an infinite loop with this function.Ranite
P
21

You can use the following

<pre id="dump"></pre>
<script>
   var dump = JSON.stringify(sampleJsonObject, null, 4); 
   $('#dump').html(dump)
</script>
Palmar answered 23/7, 2012 at 5:42 Comment(0)
I
15

In Firebug, if you just console.debug ("%o", my_object) you can click on it in the console and enter an interactive object explorer. It shows the entire object, and lets you expand nested objects.

Inquire answered 24/9, 2008 at 22:48 Comment(3)
Problem with that is it only shows the 'topmost' object - i've got dozens of nested objects, and I really need to be able to see the entire contents at once, and importantly, see where things are changing. So Firebug really isn't working for me in this case.Spectrogram
(yes I know you can click to expand them, but clicking 10 or so links every time I want to dump the data is what I'm doing now - very slow progress)Spectrogram
This also works in Chrome (and therefore presumably in Safari).Kyles
F
12

For Node.js, use:

util.inspect(object, [options]);

API Documentation

Florentinoflorenza answered 5/10, 2011 at 18:55 Comment(0)
H
10

For those looking for an awesome way to see your object, check prettyPrint.js

Creates a table with configurable view options to be printed somewhere on your doc. Better to look than in the console.

var tbl = prettyPrint( myObject, { /* options such as maxDepth, etc. */ });
document.body.appendChild(tbl);

enter image description here

Hammett answered 21/6, 2013 at 13:12 Comment(0)
E
5

I'm programming in Rhino and I wasn't satisfied with any of the answers that were posted here. So I've written my own pretty printer:

function pp(object, depth, embedded) { 
  typeof(depth) == "number" || (depth = 0)
  typeof(embedded) == "boolean" || (embedded = false)
  var newline = false
  var spacer = function(depth) { var spaces = ""; for (var i=0;i<depth;i++) { spaces += "  "}; return spaces }
  var pretty = ""
  if (      typeof(object) == "undefined" ) { pretty += "undefined" }
  else if ( typeof(object) == "boolean" || 
            typeof(object) == "number" ) {    pretty += object.toString() } 
  else if ( typeof(object) == "string" ) {    pretty += "\"" + object + "\"" } 
  else if (        object  == null) {         pretty += "null" } 
  else if ( object instanceof(Array) ) {
    if ( object.length > 0 ) {
      if (embedded) { newline = true }
      var content = ""
      for each (var item in object) { content += pp(item, depth+1) + ",\n" + spacer(depth+1) }
      content = content.replace(/,\n\s*$/, "").replace(/^\s*/,"")
      pretty += "[ " + content + "\n" + spacer(depth) + "]"
    } else { pretty += "[]" }
  } 
  else if (typeof(object) == "object") {
    if ( Object.keys(object).length > 0 ){
      if (embedded) { newline = true }
      var content = ""
      for (var key in object) { 
        content += spacer(depth + 1) + key.toString() + ": " + pp(object[key], depth+2, true) + ",\n" 
      }
      content = content.replace(/,\n\s*$/, "").replace(/^\s*/,"")
      pretty += "{ " + content + "\n" + spacer(depth) + "}"
    } else { pretty += "{}"}
  }
  else { pretty += object.toString() }
  return ((newline ? "\n" + spacer(depth) : "") + pretty)
}

The output looks like this:

js> pp({foo:"bar", baz: 1})
{ foo: "bar",
  baz: 1
}
js> var taco
js> pp({foo:"bar", baz: [1,"taco",{"blarg": "moo", "mine": "craft"}, null, taco, {}], bleep: {a:null, b:taco, c: []}})
{ foo: "bar",
  baz: 
    [ 1,
      "taco",
      { blarg: "moo",
        mine: "craft"
      },
      null,
      undefined,
      {}
    ],
  bleep: 
    { a: null,
      b: undefined,
      c: []
    }
}

I've also posted it as a Gist here for whatever future changes may be required.

Emergent answered 24/9, 2008 at 22:47 Comment(1)
It might be a pretty printer but the code doesn't actually look very pretty :)Flyleaf
A
3

jsDump

jsDump.parse([
    window,
    document,
    { a : 5, '1' : 'foo' },
    /^[ab]+$/g,
    new RegExp('x(.*?)z','ig'),
    alert, 
    function fn( x, y, z ){
        return x + y; 
    },
    true,
    undefined,
    null,
    new Date(),
    document.body,
    document.getElementById('links')
])

becomes

[
   [Window],
   [Document],
   {
      "1": "foo",
      "a": 5
   },
   /^[ab]+$/g,
   /x(.*?)z/gi,
   function alert( a ){
      [code]
   },
   function fn( a, b, c ){
      [code]
   },
   true,
   undefined,
   null,
   "Fri Feb 19 2010 00:49:45 GMT+0300 (MSK)",
   <body id="body" class="node"></body>,
   <div id="links">
]

QUnit (Unit-testing framework used by jQuery) using slightly patched version of jsDump.


JSON.stringify() is not best choice on some cases.

JSON.stringify({f:function(){}}) // "{}"
JSON.stringify(document.body)    // TypeError: Converting circular structure to JSON
Antecede answered 18/2, 2010 at 22:0 Comment(0)
S
2

Taking PhiLho's lead (thanks very much :)), I ended up writing my own as I couldn't quite get his to do what I wanted. It's pretty rough and ready, but it does the job I need. Thank you all for the excellent suggestions.

It's not brilliant code, I know, but for what it's worth, here it is. Someone might find it useful:

// Usage: dump(object)
function dump(object, pad){
    var indent = '\t'
    if (!pad) pad = ''
    var out = ''
    if (object.constructor == Array){
        out += '[\n'
        for (var i=0; i<object.length; i++){
            out += pad + indent + dump(object[i], pad + indent) + '\n'
        }
        out += pad + ']'
    }else if (object.constructor == Object){
        out += '{\n'
        for (var i in object){
            out += pad + indent + i + ': ' + dump(object[i], pad + indent) + '\n'
        }
        out += pad + '}'
    }else{
        out += object
    }
    return out
}
Spectrogram answered 24/9, 2008 at 23:37 Comment(4)
By the way, even though you can, you shouldn't end lines without a semicolon. Also, the standard way of doing __ if (!pad) pad = '' __ would be: __ pad = (pad || '') __Canst
I take your point on if (!foo) foo = ... vs foo = (foo || ...), but what's the rationale for ending all lines with semicolons?Spectrogram
You will run into some nasty idiosyncrasies of the language if you don't, not to mention you will be unable to easily minify your code (unless the minifier you happen to use is nice enough to stick semicolons in for you). See stackoverflow.com/questions/42247 for more details.Canst
if (!pad) pad = ''; is cheaper, more flexible & more readable than pad = (pad || ''); albeit by a minute amount. If you insist on that form, remove the extraneous parenthesis. pad = pad || ''; 3 reasons for semicolons: JS auto-inserts end-line semicolons when it sees that omitting them would throw an error. 1) This is perforce a teeny bit slower than adding it yourself, and 2) can lead to errors when the next line happens not to throw an error when combined. 3) will prevent your code from being minified.Cordiform
I
2

For anyone checking this question out in 2021 or post-2021

Check out this Other StackOverflow Answer by hassan

TLDR:

JSON.stringify(data,null,2)

here the third parameter is the tab/spaces

Icsh answered 12/2, 2021 at 9:54 Comment(0)
E
1

This is really just a comment on Jason Bunting's "Use Crockford's JSON.stringify", but I wasn't able to add a comment to that answer.

As noted in the comments, JSON.stringify doesn't play well with the Prototype (www.prototypejs.org) library. However, it is fairly easy to make them play well together by temporarily removing the Array.prototype.toJSON method that prototype adds, run Crockford's stringify(), then put it back like this:

  var temp = Array.prototype.toJSON;
  delete Array.prototype.toJSON;
  $('result').value += JSON.stringify(profile_base, null, 2);
  Array.prototype.toJSON = temp;
Executive answered 18/2, 2010 at 21:46 Comment(0)
G
1

I thought J. Buntings response on using JSON.stringify was good as well. A an aside, you can use JSON.stringify via YUIs JSON object if you happen to be using YUI. In my case I needed to dump to HTML so it was easier to just tweak/cut/paste PhiLho response.

function dumpObject(obj, indent) 
{
  var CR = "<br />", SPC = "&nbsp;&nbsp;&nbsp;&nbsp;", result = "";
  if (indent == null) indent = "";

  for (var property in obj)
  {
    var value = obj[property];

    if (typeof value == 'string')
    {
      value = "'" + value + "'";
    }
    else if (typeof value == 'object')
    {
      if (value instanceof Array)
      {
        // Just let JS convert the Array to a string!
        value = "[ " + value + " ]";
      }
      else
      {
        var od = dumpObject(value, indent + SPC);
        value = CR + indent + "{" + CR + od + CR + indent + "}";
      }
    }
    result += indent + "'" + property + "' : " + value + "," + CR;
  }
  return result;
}
Granese answered 29/3, 2011 at 16:24 Comment(0)
F
1

Lots of people writing code in this thread, with many comments about various gotchas. I liked this solution because it seemed complete and was a single file with no dependencies.

browser

nodejs

It worked "out of the box" and has both node and browser versions (presumably just different wrappers but I didn't dig to confirm).

The library also supports pretty printing XML, SQL and CSS, but I haven't tried those features.

Finkelstein answered 21/3, 2013 at 16:0 Comment(0)
U
0

A simple one for printing the elements as strings:

var s = "";
var len = array.length;
var lenMinus1 = len - 1
for (var i = 0; i < len; i++) {
   s += array[i];
   if(i < lenMinus1)  {
      s += ", ";
   }
}
alert(s);
Ugric answered 29/8, 2014 at 18:29 Comment(0)
H
0

My NeatJSON library has both Ruby and JavaScript versions. It is freely available under a (permissive) MIT License. You can view an online demo/converter at:
http://phrogz.net/JS/neatjson/neatjson.html

Some features (all optional):

  • Wrap to a specific width; if an object or array can fit on the line, it is kept on one line.
  • Align the colons for all keys in an object.
  • Sort the keys to an object alphabetically.
  • Format floating point numbers to a specific number of decimals.
  • When wrapping, use a 'short' version that puts the open/close brackets for arrays and objects on the same line as the first/last value.
  • Control the whitespace for arrays and objects in a granular manner (inside brackets, before/after colons and commas).
  • Works in the web browser and as a Node.js module.
Heat answered 20/4, 2015 at 16:17 Comment(0)
D
-5

flexjson includes a prettyPrint() function that might give you what you want.

Doings answered 24/9, 2008 at 22:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.