TIP: How to Get Readable Format from JSON.stringify()

TIP: How to Get Readable Format from JSON.stringify()

I suspect most of you JavaScript and JavaScript for Automation (JXA) scripters are at least somewhat familiar with the powerful JSON functions, like JSON.stringify() - JavaScript | MDN.

However, some of you, like me, may not be aware of some options of the JSON.stringify() function to generate a very readable output, and to filter the output. So, here is an example.

JavaScript JSON.stringify Options

//--- NORMAL, COMPACT, JSON.stringify() ---
console.log(JSON.stringify(myObject));
/* {"kMDItemContentType":"public.jpeg","kMDItemContentTypeTree":["public.jpeg","public.image","public.data","public.item","public.content"],"kMDItemCreator":"Adobe Photoshop CS3 Macintosh"} */

//--- JSON.stringify() WITH NICE, READABLE FORMAT ---
console.log(JSON.stringify(myObject, undefined, '\t'))
/* {
  "kMDItemContentType": "public.jpeg",
  "kMDItemContentTypeTree": [
    "public.jpeg",
    "public.image",
    "public.data",
    "public.item",
    "public.content"
  ],
  "kMDItemCreator": "Adobe Photoshop CS3 Macintosh"
} */

//--- JSON.stringify() WITH OUTPUT FILTERED FOR SPECIFIED KEYS ---
console.log(JSON.stringify(myObject, ["kMDItemContentType", "kMDItemCreator"], '\t'))
/* {
    "kMDItemContentType": "public.jpeg",
    "kMDItemCreator": "Adobe Photoshop CS3 Macintosh"
} */

For more info, see JSON.stringify() - JavaScript | MDN.

I hope you find this helpful.
Please feel free to post any comments you may have about this.

1 Like

I find it useful to have this generic function on hand – the arguments just give structurally indented outputs for dictionaries and lists:

    // show :: a -> String
    const show = x => JSON.stringify(x, null, 2);

for example (you can use the Babel JS online repl to back-convert to ES5 if you are on a pre-Sierra system)

(() => {
    'use strict';

    // show :: a -> String
    const show = x => JSON.stringify(x, null, 2);

    return show({alpha:1, beta:2, gamma:3});
})();

Result:

{
  "alpha": 1,
  "beta": 2,
  "gamma": 3
}
// show :: a -> String
    const show = x => JSON.stringify(x, null, 2);

Thanks, Rob.

For those of us still running macOS El Capitan, here's the code:

// ES5 Compliant
(function run() {
    'use strict';

    // show :: a -> String
    function show(x) {
      console.log(JSON.stringify(x, null, 2));
      return
    }

    show({alpha:1, beta:2, gamma:3});
    
})();

BTW, I had to add the console.log in order to get the nice format.

As a follow-up to this nice tool, I have found it more convenient to just use a text expander rather than worry about creating/getting/finding a function.

Here's my TextExpander snippet:

console.log(JSON.stringify(%filltext:name=JS Object Variable:default=myObject%, undefined, 4))

//--- expands to ---

console.log(JSON.stringify(myObject, undefined, 4))

Uses this popup:

Well ... the problem was simply that you weren't returning a value (you missed the return keyword at the start of the show line.

Adding console.log introduces two problems in your snippet:

  • The show function now returns the value undefined rather than a string representation of its argument object, so it has little further value to other functions – they can't make use of the (formerly string) value which it returns
  • the 'type signature' comment line is now incorrect – show has ceased to return a 'String' value and is now a 'command' of type IO (undefined) rather than a function. It has a side-effect but returns no useful value to any calling function.

What you actually need in ES5 is something like:

// ES5 Compliant
(function run() {
    'use strict';

    // show :: a -> String
    function show(x) {
      return JSON.stringify(x, null, 2);
    }

    return show({alpha:1, beta:2, gamma:3});
})();

so that show takes an argument of any kind, and returns a string value.

For example:

// ES5 Compliant
(function run() {
    'use strict';

    // show :: a -> String
    function show(x) {
      return JSON.stringify(x, null, 2);
    }

    // How many digits in 2017 ?

    return show(2017).length;
 
})();

Atom displays the raw string value returned, without further post-processing:

No, I don't think so, runing Script Editor 2.8.1 (183.1) on macOS 10.11.6:

Running your EXACT code does NOT provide a nice output format:

No problems for me, since the ONLY purpose of the function show is to produce a nice formatted output in the results area.

But, it really doesn't matter since I don't use your function anyway.
I just use the text expansion as shown above, which works flawlessly.

Running your EXACT code does NOT 'provide' a nice output format:

Perhaps you are being confused by the output channel ? :slight_smile:

The show function returns the formatted string, as you will see in the direct Atom script package output.

Script Editor doesn't directly display raw program output, it interprets the output as a JS value, and generates its own rendering of that, passing it back through JSON.stringify again. Here SE is converting the newlines to an "\n" literal representation of them.

What you need is a conceptual distinction between two adjoint channels:

  • Values returned and usable for futher computation, vs
  • IO events in the logging channel.

For enhanced logging, incidentally, it can be useful to define a log command which uses JSON.stringify.

(function () {
    'use strict';

    // show :: a -> String
    function show(x) {
        return JSON.stringify(x, null, 2);
    };

    // log :: a -> IO ()
    function log(x) {
        console.log(show(x));
    };
})();

Nope. I'm not confused. Are you?

I'm use the Apple standard Script Editor, which most JXA users would be using.
I gave you a screenshot of what happens in SE with your code.
If you are using a 3rd party app which formats output differently, then you may want to specify that when you make your statements.

IAC, my solution works in SE, and I would think in any JXA editor that honors the standard console.log statement.

I have no particular view on preferable solutions to unspecified problems – whatever works – the problem was simply that you presented your version as an ES6 to ES5 (Sierra to Capitan) translation, with necessary tweaks, and that would be misleading to readers. You actually created a different type of function - one which returned undefined rather than returning a string.

There's a useful and practical distinction between a function which returns a formatted string for further use within a program, and one which returns an unusable undefined value.

show and log (above) are just two different functions.

  • show creates and returns an indented string
  • log pumps it out to the console stream, returning undefined

Each has their uses. No need to fret :slight_smile:



The correct ES5 translation would be more like:

(function () {
    'use strict';

    // show :: a -> String
    function show(x) {
        return JSON.stringify(x, null, 2);
    };

    // log :: a -> IO ()
    function log(x) {
        return console.log(show(x));
    };

    return {
        show: show(2017),
        log: log(2017)
    };
})();

No problems for me, since the ONLY purpose of the function show is to produce a nice formatted output in the results area.

No, it's much more widely useful than that.

In the context of a Keyboard Maestro forum:

@ComplexPoint, perhaps you would like to start your own topic using the tools you prefer, rather than continue to purposefully misinterpret my statements.

My post of a script that was compliant with ES5 was not a literal translation of your ES6 script, as I noted at the bottom of my ES5 script:

You of all people should know that if you don't return anything at the end of a script, then Script Editor always shows "undefined" in the Result block:

This is NOT a problem. It is just how Script Editor works.

Since this is my topic, let me make it clear to you the purpose of my topic:

Show how to use JSON.stringingy() in the Script Editor app to get more readable format, and to filter the results.

Wrong again. My script works just fine in a KM Action:

Here's my same script run in Script Editor:

So my script works equally well in both Script Editor and KM.

If you wish to create other functions to use with other apps, that's great.
Why don't you start your own topic to discuss them? It will make both topics
more clear to all readers.