I got tired of attempting to get shell scripts to produce valid JSON. You’ve likely seen something like this before:

echo '{"name":"Jane"}'

It gets merrier if an element contains an environment variable: open double, close single, add variable, open single, blergh.

A here! script will do it as will a a printf(1), but neither much improve legibility, and if strings contain quotes, it becomes almost impossible to make a script produce JSON.

printf '{"name": "%s"}

' "Jane"

Enter jo:

$ jo name = Jane { "name" : "Jane" }

The idea occurred to me late at night, and I don’t know why this has taken so long to happen:

$ jo time = $( date +%s ) dir = $HOME { "time" :1457195712, "dir" : "/Users/jpm" }

Bam! Jo tries to be clever about types and knows null , booleans, strings and numbers. It does arrays, and it pretty-prints on demand:

$ jo -p -a spring summer winter [ "spring" , "summer" , "winter" ]

Inspired by a comment on HN, I added another hack: if a key’s value begins with an opening brace ( { ) or a bracket ( [ ]) we attempt to decode JSON from it; this allows jo to add objects or arrays (use -a !) to itself. Watch:

$ jo -p name = JP object = $( jo fruit = Orange point = $( jo x = 10 y = 20 ) number = 17 ) sunday = false { "name" : "JP" , "object" : { "fruit" : "Orange" , "point" : { "x" : 10, "y" : 20 } , "number" : 17 } , "sunday" : false }

jo also supports nested types natively:

$ jo -p number = 17 pass = true geo[lon] = 88 geo[cc] = ES point[] = 1 point[] = 2 geo[lat] = 123.45 { "number" : 17, "pass" : true , "geo" : { "lon" : 88, "cc" : "ES" , "lat" : 123.45 } , "point" : [ 1, 2 ] }

Why did I do this? I need lots of JSON for testing OwnTracks, and this just looks better in scripts.

$ jo _type = location \ cog = $(( RANDOM % 360 )) \ t = u \ lat = 48.85833 \ lon = 2.29513 \ acc = 5 \ tid = JJ \ tst = $( date +%s ) | mosquitto_pub -t owntracks/jjolie/test -l

A suggestion by Andrew Bibby brought along the possibility of obtaining JSON element values from files, so I added @file to read a file (sans traling newline or carriage return) into a value as well as something which I use a lot, convert binary files to base64, using the %file syntax:

$ jo _type = card name = "Vanessa" face = %vanessa.png { "_type" : "card" , "name" : "Vanessa" , "face" : "iVBORw0KGgoAAA ... QmCC" }

And it has a man page. Go get it. Jo!

Updates: