Writing a JSON Pretty Printer

Kurt Cagle
The Cagle Report
Published in
5 min readApr 27, 2017

Sometimes I don’t have any grand plan when I write articles. I may just want to capture a bit of code that I think is interesting, may want to kibitz about the state of the world, and every so often I do something for the heck of it because — well, it’s like three in the morning, I really should be in bed, but there’s just time for one more article (I’m doomed).

This why I wrote a JSON Pretty Printer.

Yes, there are likely dozens of libraries out there to do pretty printing in Javascript, but I wanted something quick and dirty to help me convert compressed JSON into something structured for a client. The following prettyPrinter function does precisely that:

'use strict';
function prettyPrint(str){
var s1 = str.replace(new RegExp(`(\\{)|(\\()|(\\[)`,'g'),'$1$2$3\n');
var s2 = s1.replace(new RegExp(`(\\})|(\\))|(\\])`,'g'),'$1$2$3\n');
var s3 = s2.replace(new RegExp(',','g'),',\n');
var tokens = s3.split('\n');
var offsets = [];
var index=0;
var offets = (tokens).forEach((token)=>{
offsets.push('\t'.repeat(index)+token.trim());
if (token.match('[\\{|\\(|\\[]')){index++};
if (token.match('[\\}|\\)|\\]]')){index--};
})
return offsets.join('\n')
};
var data = {"local-cluster-default":{"id":"12094251720001288998","test":["foo","bar"],"name":"desktop-uq34ism-cluster", "version":"10.0-20170329", "effective-version":9000100, "role":"local"},"meta":{"uri":"/manage/v2", "current-time":"2017-04-26T16:14:24.2808436-07:00"}};
var str = JSON.stringify(data);
prettyPrint(str)

And here’s the output:

{
"local-cluster-default":{
"id":"12094251720001288998",
"test":[
"foo",
"bar"]
,
"name":"desktop-uq34ism-cluster",
"version":"10.0-20170329",
"effective-version":9000100,
"role":"local"}
,
"meta":{
"uri":"/manage/v2",
"current-time":"2017-04-26T16:14:24.2808436-07:00"}
}

It probably doesn’t mean a damn thing — I could have loaded in a library to do precisely the same thing, but there are times where I feel going back to basics is the right thing to do.

The algorithm for doing a JSON pretty print is fairly simple, and illustrates the fact that regular expressions are still a text programmer’s most powerful tools. For instance, the line:

var s1 = str.replace(new RegExp(`(\\{)|(\\()|(\\[)`,'g'),'$1$2$3\n');

serves to identify any opening brace, bracket or parenthesis and places a newline character after it. It should be noted, that there are even more shortcuts here. For instance, you replace the above with:

var s1 = str.replace(new RegExp(`(\\{)|(\\()|(\\[)`,'g'),'$1$2$3\n');
var s2 = s1.replace(new RegExp(`(\\})|(\\))|(\\])`,'g'),'$1$2$3\n');
var s3 = s2.replace(new RegExp(',','g'),',\n');
var tokens = s3.split('\n');

with

var s1 = str.replace(new RegExp(`([\\{|\\(|(\\[\\}|\\)|\\]|,])`,'g'),'$1\n');
var tokens = s1.split('\n');

The effect of this is to match any of the characters (‘{‘, ‘(‘, ‘[‘, ‘}’, ‘)’, ‘]’, ‘,’) and add a new line character after it. The split() function then breaks this apart into a set of lines.

The indexer creates what amounts to a basic stack — every time an open delimiter is encountered, the stack pointer is incremented. This is then used to determine the indentation space (given as a tab, or \t character). Every time a close delimiter is encountered, the stack pointer is decremented. This way, you can go arbitrarily deep with the folding structure.

A reader reminded me that the JSON.stringify function can do much the same thing:

JSON.stringify(obj,null,'\t')

for instance will create output with tabs, which is what my pretty-print function . I felt it was still worth working it out, if only because I’ve always wondered what went into creating such a function (and going to show that even people who work with a language on a daily basis can learn something new).

For more information on stringify, check out the Mozilla JSON.stringify() function page.

Two other notes — I’m not checking for brace, bracket or parentheses characters within the JSON field values (I leave that as an exercise for the reader), and I am implicitly assuming that the JSON is well formed.

The same pretty print approach can also be done in XQuery, by the way. The following is the same algorithm recast, targeting the MarkLogic API.

xquery version "1.0-ml";
declare namespace pp = "http://marklogic.com/ns/prettyprint";
declare function pp:pretty-print($str as xs:string) as xs:string {
let $s1 := fn:replace($str,'(\{|\(|\[)','$1
')
let $s2 := fn:replace($s1,'(\}|\)|\])','$1
')
let $s3 := fn:replace($s2,',',',
')
let $tokens := fn:tokenize($s3,'
')
let $map := map:new((map:entry("index",0),map:entry("buffer","")))
let $_ := for $token in $tokens return
(
for $new-index in (0 to map:get($map,"index")) return
map:put($map,"buffer",map:get($map,"buffer")||"	"),
map:put($map,"buffer",map:get($map,"buffer")||
fn:normalize-space($token)||'
'),
if (fn:matches($token,'[\{|\(|\[]')) then
map:put($map,"index",map:get($map,"index") + 1)
else if (fn:matches($token,'[\}|\)|\]]')) then
map:put($map,"index",map:get($map,"index") - 1)
else ()
)
return map:get($map,"buffer")
};
let $data := '{"local-cluster-default":{"id":"12094251720001288998","test":["foo","bar"],"name":"desktop-uq34ism-cluster", "version":"10.0-20170329", "effective-version":9000100, "role":"local"},"meta":{"uri":"/manage/v2", "current-time":"2017-04-26T16:14:24.2808436-07:00"}}'
return pp:pretty-print($data)

This cheats a bit by using maps, getting around the immutable nature of most XQuery calls. It also uses the carriage return (
) and tab (	) entities, as use of \n and \t caused some minor problems. In both cases, the $index variable tracks how many opening braces vs. closing braces of a given type are encountered.

On a related note, another reader asked how I made the pretty print in the cool font given above. The font is one my favorites — the Black Adder ITC Regular font, after the TV show featuring Rowan Atkinson where it was featured in the title credits. Black Adder is a commercial font, but there are open source fonts that are similar. The following shows how this gets displayed as a web page:

The trick is to convert this into a web font (go onto Google and type ‘Converting to Web Font — there are dozens of online services which will do it for free). Such fonts are varied: SVG fonts, Open Type (OTF) or a WOFF fonts, just to name a few. Many browser also now natively support True Type fonts (TTF. Once these fonts are created, they can be uploaded into a resource folder on a web site. The web font can then be identified using the CSS @font-face directive. The HTML code for the above graphic as follows:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Black Adder</title>
<style type="text/css">
@font-face
{
font-family: 'Conv_ITCBLKAD';
src: url('fonts/ITCBLKAD.svg') format('svg'),
url('fonts/ITCBLKAD.woff') format('woff'),
url('fonts/ITCBLKAD.ttf') format('truetype');
font-weight: normal;
font-style: normal;
}

body
{
font-family: Verdana, Tahoma, Arial, Sans-Serif;
color: black;
}

.demo
{
background-image: url('parchment.png');
font-family: 'Conv_ITCBLKAD', Sans-Serif;
font-size: 10pt;
width: 500px;
margin-left: 10px auto;
text-align: left;
border: 1px solid #666;
padding: 5px;
white-space: pre;

}

.text
{
opacity: 0.85;
font-size: 18pt;
display: block;
margin-bottom: -8pt;
padding: -10px;
}
.title {
font-size:24pt;
}
</style>
</head>
<body>
<div class="demo">
<div class="title">Pretty Printing</div>
<div class="text"></div>
</div>
<script type="application/javascript">
var character = {
lena: {
name: "Lena Calchesthes",
species: "mage",
wealth: { gold: 300, silver: 75, copper: 364 }
}
};
var pre = document.querySelector(".text");
pre.innerHTML = JSON.stringify(character,null,'\t')
</script>
</body>
</html>

The font-face is then referenced via the font-family property in the CSS.

font-family: 'Conv_ITCBLKAD', Sans-Serif;

Now, I would not in fact recommend using Black Adder for your code output, unless you were really sadistic and liked your readers to suffer.

Kurt Cagle writes code. Periodically.

--

--