3

There are lots of old questions about dumping JavaScript objects.

But of the ones which specify dumping the contents of functions, I can only find ones that emit those functions as strings in quotes (with internal quotes escaped).

What I want is something that dumps objects, including functions, in full, in a plain old JavaScript format ready to be pretty printed.

  • I do not need full serialization and deserialization like some previous questions.
  • I do need to see the object as text. Just using console.log() requires interaction to unfold object members etc. Saving the console buffer as text after console.log() does not result in plain js syntax that can be pretty printed.
  • Emit placeholders or such for references that can't be dumped (circular refs, DOM refs, etc). Shouldn't just barf when it hits one.

Here's the naive approach that dumps function text but as quoted strings that a pretty printer will not format as code:

JSON.stringify(someObject, function(key, val) {
  return (typeof val === 'function') ? val.toString() : val;
}, 4);

For the test object {a: 1, b: 'bb', c: "bb", f: function e() {return "x";} } the naive approach outputs:

{
    "a": 1,
    "b": "bb",
    "c": "bb",
    "f": "function e() {return \"x\";}"
}

What I need is:

{
    "a": 1,
    "b": "bb",
    "c": "bb",
    "f": function e() {
        return "x";
    }
}

I'm happy for this to be closed as a duplicate if one of the previous answers does what I want. I looked at many of them and can't find it.

(Use case: Making a TaperMonkey userscript for a third party site. I need to see what is actually exposed to find places to add userscript hooks. I will beautify the output, print it out, load it into a code editor, etc)

Community
  • 1
  • 1
hippietrail
  • 15,848
  • 18
  • 99
  • 158
  • 1
    I must be misunderstanding something. Does your approach not work? – Mike Cluck May 05 '15 at 16:16
  • No. It outputs quoted functions so a pretty printer just sees strings and doesn't reformat them. I'm going to add sample output to my question. – hippietrail May 05 '15 at 16:17
  • Why are you passing the string to `JSON.stringify`? Isn't that why your quotes are getting escaped? – Evan Davis May 05 '15 at 16:17
  • So you want a kind of reverse-`eval` for object values? – apsillers May 05 '15 at 16:23
  • @Mathletics: (sorry my previous reply was wrong.) `JSON.stringify()` is the best I've found in previous answers. It converts the bulk of the object to a string which can be saved, cut and paste, etc; rather than an interactive console object. – hippietrail May 05 '15 at 16:25

2 Answers2

4

Don't use JSON.stringifiy - JSON cannot represent functions, and therefore you will never get it into returning what you want.

Since you are targeting only a specific browser, maybe you can use uneval or .toSource. If you're not using Firefox, have a look at this polyfill.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Exactly. JSON is not the same as "JavaScript notation". It has significantly more restrictions. – tadman May 05 '15 at 16:51
  • On my bogged-down little netbook on my sketchy connection from Vietnam I'm now looking into this and whether there are equivalents for either in Chrome. – hippietrail May 05 '15 at 17:07
  • @hippietrail: Oh, somehow I was under the expression that you were using Firefox userscripts. – Bergi May 05 '15 at 17:09
  • I guess Firefox had userscripts first back in the day. I'm using TaperMonkey on Chrome these days though. – hippietrail May 05 '15 at 17:13
  • When I try pasting the polyfill code into the Chrome console it barfs on line 159 `[he, props] = MarkSharpObjects(obj);` even though it's inside a `try` and neither `unveval()` nor `{}.toSource()` work. – hippietrail May 05 '15 at 17:32
  • Oh, a destructuring assignment is not exactly ES5 :-) But you can [easily fix that](https://github.com/jorendorff/sharps/pull/1). – Bergi May 05 '15 at 17:41
  • @Bergi: Hmm after fixing those I now get an exception when I try to use it: `Uncaught DOMException: Failed to read the 'contentDocument' property from 'HTMLIFrameElement': Blocked a frame with origin "http://www.memrise.com" from accessing a cross-origin frame.` – hippietrail May 05 '15 at 17:56
  • What are you trying to serialise? Some part of your object seems to reference DOM elements - and those are ill-suited for getting an object representation. In your case, the recursive traversal tries to inspect an iframe with a cross-origin content. – Bergi May 05 '15 at 21:07
  • @Bergi: As I say in the question I'm investigating the objects used in a third party web app that I want to customize by adding hooks in a TaperMonkey userscript. I'm not doing true serialization, that was just the most appropriate tag. It's acceptable for some references to be dumped as placeholders. I didn't know what I was going to run into until I got this far thanks to the answers so far. – hippietrail May 06 '15 at 01:33
  • @hippietrail: You might want to modify the code so that when it encounters a `Node` it uses an [`XMLSerializer`](https://developer.mozilla.org/en-US/docs/XMLSerializer) instead of the JSON-like dumps. – Bergi May 06 '15 at 01:48
  • Hmm gotta figure out how to detect that. – hippietrail May 06 '15 at 01:59
  • On Firefox, `.toSource()` did exactly what I wanted. I couldn't find a similar solution on Chrome, but for my use case it was acceptable to switch browsers to get the object dump. – hippietrail May 06 '15 at 13:04
1

You could accomplish this with some simple string replacement.

var obj = {
    name: 'Bond',
    getName: function() {
        return 'James ' + this.name;
    },
    getDrink: function() {
        return "Martini";
    }
};
var result = JSON.stringify(obj, function(key, val) {
    return (typeof val === 'function') ? val.toString() : val;
}, 4);
result = result
    .replace(/"function/g, 'function')
    .replace(/}"/g, '}')
    .replace(/\\n/g, '\n')
    .replace(/\\"/g, '"');
document.querySelector('pre').innerText = result;

// Or if you actually want it as an object
eval('var obj = ' + result);
console.log(obj);
<pre></pre>
Mike Cluck
  • 31,869
  • 13
  • 80
  • 91
  • 3
    This is really a last resort technique as that regular expression will gleefully mangle anything in your JSON data that looks vaguely like a function. – tadman May 05 '15 at 16:53
  • This is true but I figure a last resort technique is okay for a last resort kind of a problem. – Mike Cluck May 05 '15 at 16:54
  • 1
    I was thinking about postprocessing but already worried that it might misprocess some stuff. On my real world object after pretty printing the indent runs wild but that could be due to a quirk in jsbeautify rather than an actual problems caused by the regex. For now it looks like this approach is OK for my current problem. – hippietrail May 05 '15 at 17:05
  • Some of my real-word objects contain circular references which case `JSON.stringify()` to barf. I'm not expecting impossible support for those but their existence prevents me from getting a dump of the object at all. – hippietrail May 05 '15 at 17:38
  • 1
    There are ways of detecting circular references and resolving those. [It's a bit of a complex problem.](http://stackoverflow.com/questions/3340485/how-to-solve-circular-reference-in-json-serializer-caused-by-hibernate-bidirecti) – Mike Cluck May 05 '15 at 17:40
  • Just emitting some placeholder comment of string that doesn't break the JS for formatting purposes is acceptable for when a circular ref is there. I've added that to my question. – hippietrail May 05 '15 at 17:43