August 2011: Using with other tools
November 2011: Creating json is now ten times easier.

Original A json parser for the shell. A json parser for the shell. getopt ninja Now with mapping across elements Friendlier scripting. Using with other tools Creating json is now ten times easier. Twice as fast, 1/6th the memory Editable
version 6 & 7 of 8

Jshon parses, reads and creates JSON. It is designed to be as usable as possible from within the shell and replaces fragile adhoc parsers made from grep/sed/awk as well as heavyweight one-line parsers made from perl/python. Requires Jansson

Jshon loads json text from stdin, performs actions, then dumps to stdout, then displays the last action on stdout. Some of the options return output json, others output plain text meta information. Because Bash has very poor nested datastructures, Jshon does not try to return a native bash datastructure containing the json as a tpical library would. Instead, Jshon provides a history stack containing all the manipulations.

ACTIONS

Each action takes the form of a short option. Some require arguments. While many instances of jshon can be piped through each other, actions should be chained sequentially to reduce calls. All examples will work on this json sample:

{"a":1,"b":[true,false,null,"none"],"c":{"d":4,"e":5}}
jshon [actions] < sample.json

-t

(type) returns string, object, list, array, number, bool, null

jshon -t -> object

-l

(length) returns an integer. Only works on string, object, list, array.

jshon -l -> 3

-k

(keys) returns a newline separated list of keys. Only works on object.

jshon -k -> a b c

-e index

(extract) returns json value at "index". Only works on object, list, array. Negative array indexes wrap around.

jshon -e c -> {"d":4,"e":5}

-a

(across) maps the remaining actions across the selected element. Only works on objects and lists arrays. Multiple -a calls can be nested, though the need is rare in practice.

jshon -e b -a -t -> bool bool null string

-s value

(string) returns a json encoded string. Ignores stdin. Can also be -inserted to an existing structure.

jshon -s "back\\slash" -> "back\\\\slash"

## -n value (nonstring/number) returns a json element to be later -inserted into an existing structure. Valid values are true, false, null, array, object, integers and floats. Abbreviates t, f, n, [] and {} respectively also work.

jshon -n object -> {}

 

-u

(unstring) returns a decoded string. Only works on string. Works on string, int, real, boolean, null.

Kyle (2011-10-19-15-19-56-416)

Works on any primitive data type.



 

jshon -e b -e 3 -u -> none

-p

(pop) pops the last manipulation from the stack, rewinding the history. Useful for extracting multiple values from one object.

jshon -e c -e d -u -p -e e -u -> 4 5

## -d (delete) removes and item in an array or object. Negative array indexes will wrap around.

## -m index,value (modify) edits an element in a list or a dict. Value must be escaped. If value is remove, delete the index. If value is true/false/null, insert the primitive type. Quoting strings is optional, except for the ambiguous case of remove/true/false/null. Lists support several magic indexes. Negative numbers wrap around the back, and append will add a new element after the last.

json -d b -> {"a":1,"c":{"d":4,"e":5}}

jshon -m a,first -> {"a":"first", ...}

-i index

(insert) is complicated. It is the reverse of extract. Extract saves a copy of the json on a stack. Extract puts a json sub-element on the stack. Insert pops json removes a sub-element from the stack, and inserts that bit of json into the new top of the stack larger array/object underneath. Use extract to dive into the json tree, modify delete/string/nonstring to change something things, and insert to push the changes back up the tree.

Arrays are handled in a special manner. Passing integers will insert a value without overwriting. Negative integers are acceptable, as is the string 'append'. To overwrite a value in an array: delete the index, -n/s the new value, and then insert at the index.

jshon -e a -i a -> the orginal json

jshon -e b -m 2,remove -m 2,remove -m append,1.2 -i b -> {"a":1, "b":[true,false,1.2], "c":{"d":4,"e":5}}

jshon -s one -i a -> {"a":"one", ...}

Non-manipulation

These are meta options that do not directly edit json. They are called at most once per invocation and are distinguished by capital letters.

-P

(jsonp) strips a jsonp callback before continuing normally.

-S

(sort) returns output sorted by key, instead of the original ordering.

-Q

(quiet) disables error reporting on stderr, so you don't have to sprinkle 2> /dev/null throughout your script.

REAL EXAMPLE

Let's say you are making a script to download backgrounds from the internet. Specifically, the pictures on EarthPorn. Reddit has a json interface:

curl http://www.reddit.com/r/earthporn.json

But now we need to extract the URLs out of there. I could fire up a text editor and dive into the json layout, but it is faster to explore the json using Jshon as a browser. If I find an object, I look at the keys. If an array, the first element.

$ jshon -t < earthporn.json
  object
$ jshon -k < earthporn.json
  kind
  data
$ jshon -e data -t < earthporn.json
  object
$ jshon -e data -k < earthporn.json
  modhash
  children
  after
  before
$ jshon -e data -e children -t < earthporn.json
  array
$ jshon -e data -e children -e 0 -t < earthporn.json
  object
$ jshon -e data -e children -e 0 -k < earthporn.json
  kind
  data
$ jshon -e data -e children -e 0 -e data -t < earthporn.json
  object
$ jshon -e data -e children -e 0 -e data -k < earthporn.json
  .... SNIP
  url
  ....
$ jshon -e data -e children -e 0 -e data -e url -u < earthporn.json
  http://i.imgur.com/.....

Cool, now we know where a single picture is in the hierarchy. (The -u on the end removes any json-safe encoding.) But how do we get all the pictures? Each url is under a different array index. So we use the -a action to map across all the elements of the array.

jshon -e data -e children -a -e data -e url -u < earthporn.json

Yup, simply replace the -e 0 and all the links are extracted. Of course, not all URLs are images so a quick regex cleans those up. The final script looks like

curl -s http://www.reddit.com/r/earthporn.json | \
jshon -e data -e children -a -e data -e url -u | \
grep <span class="del">'.[jpe|jp|pn]g$'</span> <span class="add">'.\(jpe\|jp\|pn\)g$'</span>

 

Working with *nix text processing

Jshon was made to be part of the usual text processing pipeline. However, every single -u is printing printed out to its own line. Most shell tools expect fields to be tab separated, and newlines between sets of fields. The paste tool does this. However, paste breaks down on blank lines so use sed to pad out the empty lines.

McTypo (2011-10-19-15-21-03-028)

is printed out



 

jshon ... | sed 's/^$/-/' |  paste -s -d "\t\t\n" | ....

 

The arguments need a little explaining. -s is simple, use stdin. -s is mysteriously needed for paste to correctly handle input.
-d is less obvious from the manpage, because it can take multiple characters which are looped through. The above example concatenates every three lines together.

Ralph Corderoy (2011-10-14-01-59-42-732)

paste(1)'s -s doesn't mean read stdin, it will do this by default if no files are specified, it means to go through the files sequentially; all lines from file1, then file2, instead of a line from file1, then file2, then back to file1 for another line.



 

BUGS

Numerous! Forward slashes are not escaped, but that is a Jansson bug. Object indexes with commas are broken. Memory use can be high for large and complex pathological operations, but typically Jshon uses less memory than cat .