views:

5544

answers:

9

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.

Any suggestions welcome, thanks in advance.

+3  A: 

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.

John Millikin
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.
Dan
(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)
Dan
A: 

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

andy
It's.. Java? Hmm, not ideal, but I'll have a look, cheers.
Dan
+21  A: 

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.

Jason Bunting
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.
Joel Anair
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.
Dan
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.
Jason Bunting
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.
Dan
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.
Jason Bunting
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 :)
Dan
Well, prototype is evil like that... ;)
Jason Bunting
+3  A: 

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.

PhiLho
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 :)
Dan
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].
Ryan
@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.
PhiLho
A: 

i found this code beautifier online

Miau
A: 

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
}
Dan
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 || '') __
Jason Bunting
I take your point on if (!foo) foo = ... vs foo = (foo || ...), but what's the rationale for ending all lines with semicolons?
Dan
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 http://stackoverflow.com/questions/42247/ for more details.
Jason Bunting
if (!pad) pad = ''; is cheaper, more flexible 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.
SamGoody
A: 

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;
Peter Rust
A: 

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
NV
A: 

I'd have a look at http://jsbeautifier.org/. Its source is available, so it wouldn't be hard to script it for more convenient use.

Michael Wolf