'\" t
.\" Automatically generated by Pandoc 2.12
.\"
.TH "JO" "1" "" "User Manuals" ""
.hy
.SH NAME
.PP
jo - JSON output from a shell
.SH SYNOPSIS
.PP
jo [-p] [-a] [-B] [-D] [-e] [-n] [-v] [-V] [-d keydelim] [-f file]
[\[en]] [ [-s|-n|-b] word \&...]
.SH DESCRIPTION
.PP
\f[I]jo\f[R] creates a JSON string on \f[I]stdout\f[R] from
\f[I]word\f[R]s given it as arguments or read from \f[I]stdin\f[R].
If \f[C]-f\f[R] is specified, \f[I]jo\f[R] first loads the contents of
\f[I]file\f[R] as a JSON object or array, then modifies it with
subsequent \f[I]word\f[R]s before printing the final JSON string to
\f[I]stdout\f[R].
\f[I]file\f[R] may be specified as \f[C]-\f[R] to read from
\f[I]jo\f[R]\[cq]s standard input; this takes precedence over reading
\f[I]word\f[R]s from \f[I]stdin\f[R].
.PP
Without option \f[C]-a\f[R] it generates an object whereby each
\f[I]word\f[R] is a \f[C]key=value\f[R] (or \f[C]key\[at]value\f[R])
pair with \f[I]key\f[R] being the JSON object element and
\f[I]value\f[R] its value.
\f[I]jo\f[R] attempts to guess the type of \f[I]value\f[R] in order to
create number (using \f[I]strtod(3)\f[R]), string, or null values in
JSON.
.PP
A missing or empty \f[I]value\f[R] normally results in an element whose
value is \f[C]null\f[R].
If \f[C]-n\f[R] is specified, this element is not created.
.PP
\f[I]jo\f[R] normally treats \f[I]key\f[R] as a literal string value.
If the \f[C]-d\f[R] option is specified, \f[I]key\f[R] will be
interpreted as an \f[I]object path\f[R], whose individual components are
separated by the first character of \f[I]keydelim\f[R].
.PP
\f[I]jo\f[R] normally treats \f[I]value\f[R] as a literal string value,
unless it begins with one of the following characters:
.PP
.TS
tab(@);
l l.
T{
value
T}@T{
action
T}
_
T{
\[at]file
T}@T{
substitute the contents of \f[I]file\f[R] as-is
T}
T{
%file
T}@T{
substitute the contents of \f[I]file\f[R] in base64-encoded form
T}
T{
:file
T}@T{
interpret the contents of \f[I]file\f[R] as JSON, and substitute the
result
T}
.TE
.PP
Escape the special character with a backslash to prevent this
interpretation.
.PP
\f[I]jo\f[R] treats \f[C]key\[at]value\f[R] specifically as boolean JSON
elements: if the value begins with \f[C]T\f[R], \f[C]t\f[R], or the
numeric value is greater than zero, the result is \f[C]true\f[R], else
\f[C]false\f[R].
.PP
\f[I]jo\f[R] creates an array instead of an object when \f[C]-a\f[R] is
specified.
.PP
When the \f[C]:=\f[R] operator is used in a \f[I]word\f[R], the name to
the right of \f[C]:=\f[R] is a file containing JSON which is parsed and
assigned to the key left of the operator.
The file may be specified as \f[C]-\f[R] to read from \f[I]jo\f[R]\[cq]s
standard input.
.SH TYPE COERCION
.PP
\f[I]jo\f[R]\[cq]s type guesses can be overridden on a per-word basis by
prefixing \f[I]word\f[R] with \f[C]-s\f[R] for \f[I]string\f[R],
\f[C]-n\f[R] for \f[I]number\f[R], or \f[C]-b\f[R] for
\f[I]boolean\f[R].
The list of \f[I]word\f[R]s \f[I]must\f[R] be prefixed with
\f[C]--\f[R], to indicate to \f[I]jo\f[R] that there are no more global
options.
.PP
Type coercion works as follows:
.PP
.TS
tab(@);
l l l l l.
T{
word
T}@T{
-s
T}@T{
-n
T}@T{
-b
T}@T{
default
T}
_
T{
a=
T}@T{
\[lq]a\[rq]:\[dq]\[dq]
T}@T{
\[lq]a\[rq]:0
T}@T{
\[lq]a\[rq]:false
T}@T{
\[lq]a\[rq]:null
T}
T{
a=string
T}@T{
\[lq]a\[rq]:\[lq]string\[rq]
T}@T{
\[lq]a\[rq]:6
T}@T{
\[lq]a\[rq]:true
T}@T{
\[lq]a\[rq]:\[lq]string\[rq]
T}
T{
a=\[dq]quoted\[dq]
T}@T{
\[lq]a\[rq]:\[lq]\[dq]quoted\[dq]\[rq]
T}@T{
\[lq]a\[rq]:8
T}@T{
\[lq]a\[rq]:true
T}@T{
\[lq]a\[rq]:\[lq]\[dq]quoted\[dq]\[rq]
T}
T{
a=12345
T}@T{
\[lq]a\[rq]:\[lq]12345\[rq]
T}@T{
\[lq]a\[rq]:12345
T}@T{
\[lq]a\[rq]:true
T}@T{
\[lq]a\[rq]:12345
T}
T{
a=true
T}@T{
\[lq]a\[rq]:\[lq]true\[rq]
T}@T{
\[lq]a\[rq]:1
T}@T{
\[lq]a\[rq]:true
T}@T{
\[lq]a\[rq]:true
T}
T{
a=false
T}@T{
\[lq]a\[rq]:\[lq]false\[rq]
T}@T{
\[lq]a\[rq]:0
T}@T{
\[lq]a\[rq]:false
T}@T{
\[lq]a\[rq]:false
T}
T{
a=null
T}@T{
\[lq]a\[rq]:\[dq]\[dq]
T}@T{
\[lq]a\[rq]:0
T}@T{
\[lq]a\[rq]:false
T}@T{
\[lq]a\[rq]:null
T}
.TE
.PP
Coercing a non-number string to number outputs the \f[I]length\f[R] of
the string.
.PP
Coercing a non-boolean string to boolean outputs \f[C]false\f[R] if the
string is empty, \f[C]true\f[R] otherwise.
.PP
Type coercion only applies to \f[C]key=value\f[R] words, and individual
words in a \f[C]-a\f[R] array.
Coercing other words has no effect.
.SH EXAMPLES
.PP
Create an object.
Note how the incorrectly-formatted float value becomes a string:
.IP
.nf
\f[C]
$ jo tst=1457081292 lat=12.3456 cc=FR badfloat=3.14159.26 name=\[dq]JP Mens\[dq] nada= coffee\[at]T
{\[dq]tst\[dq]:1457081292,\[dq]lat\[dq]:12.3456,\[dq]cc\[dq]:\[dq]FR\[dq],\[dq]badfloat\[dq]:\[dq]3.14159.26\[dq],\[dq]name\[dq]:\[dq]JP Mens\[dq],\[dq]nada\[dq]:null,\[dq]coffee\[dq]:true}
\f[R]
.fi
.PP
Pretty-print an array with a list of files in the current directory:
.IP
.nf
\f[C]
$ jo -p -a *
[
\[dq]Makefile\[dq],
\[dq]README.md\[dq],
\[dq]jo.1\[dq],
\[dq]jo.c\[dq],
\[dq]jo.pandoc\[dq],
\[dq]json.c\[dq],
\[dq]json.h\[dq]
]
\f[R]
.fi
.PP
Create objects within objects; this works because if the first character
of value is an open brace or a bracket we attempt to decode the
remainder as JSON.
Beware spaces in strings \&...
.IP
.nf
\f[C]
$ jo -p name=JP object=$(jo fruit=Orange hungry\[at]0 point=$(jo x=10 y=20 list=$(jo -a 1 2 3 4 5)) number=17) sunday\[at]0
{
\[dq]name\[dq]: \[dq]JP\[dq],
\[dq]object\[dq]: {
\[dq]fruit\[dq]: \[dq]Orange\[dq],
\[dq]hungry\[dq]: false,
\[dq]point\[dq]: {
\[dq]x\[dq]: 10,
\[dq]y\[dq]: 20,
\[dq]list\[dq]: [
1,
2,
3,
4,
5
]
},
\[dq]number\[dq]: 17
},
\[dq]sunday\[dq]: false
}
\f[R]
.fi
.PP
Booleans as strings or as boolean (pay particular attention to
\f[I]switch\f[R]; the \f[C]-B\f[R] option disables the default detection
of the \[lq]\f[C]true\f[R]\[rq], \[lq]\f[C]false\f[R]\[rq], and
\[lq]\f[C]null\f[R]\[rq] strings):
.IP
.nf
\f[C]
$ jo switch=true morning\[at]0
{\[dq]switch\[dq]:true,\[dq]morning\[dq]:false}
$ jo -B switch=true morning\[at]0
{\[dq]switch\[dq]:\[dq]true\[dq],\[dq]morning\[dq]:false}
\f[R]
.fi
.PP
Elements (objects and arrays) can be nested.
The following example nests an array called \f[I]point\f[R] and an
object named \f[I]geo\f[R]:
.IP
.nf
\f[C]
$ jo -p name=Jane point[]=1 point[]=2 geo[lat]=10 geo[lon]=20
{
\[dq]name\[dq]: \[dq]Jane\[dq],
\[dq]point\[dq]: [
1,
2
],
\[dq]geo\[dq]: {
\[dq]lat\[dq]: 10,
\[dq]lon\[dq]: 20
}
}
\f[R]
.fi
.PP
The same example, using object paths:
.IP
.nf
\f[C]
$ jo -p -d. name=Jane point[]=1 point[]=2 geo.lat=10 geo.lon=20
{
\[dq]name\[dq]: \[dq]Jane\[dq],
\[dq]point\[dq]: [
1,
2
],
\[dq]geo\[dq]: {
\[dq]lat\[dq]: 10,
\[dq]lon\[dq]: 20
}
}
\f[R]
.fi
.PP
Without \f[C]-d\f[R], a different object is generated:
.IP
.nf
\f[C]
$ jo -p name=Jane point[]=1 point[]=2 geo.lat=10 geo.lon=20
{
\[dq]name\[dq]: \[dq]Jane\[dq],
\[dq]point\[dq]: [
1,
2
],
\[dq]geo.lat\[dq]: 10,
\[dq]geo.lon\[dq]: 20
}
\f[R]
.fi
.PP
Create empty objects or arrays, intentionally or potentially:
.IP
.nf
\f[C]
$ jo < /dev/null
{}
$ MY_ARRAY=(a=1 b=2)
$ jo -a \[dq]${MY_ARRAY[\[at]]}\[dq] < /dev/null
[\[dq]a=1\[dq],\[dq]b=2\[dq]]
\f[R]
.fi
.PP
Type coercion:
.IP
.nf
\f[C]
$ jo -p -- -s a=true b=true -s c=123 d=123 -b e=\[dq]1\[dq] -b f=\[dq]true\[dq] -n g=\[dq]This is a test\[dq] -b h=\[dq]This is a test\[dq]
{
\[dq]a\[dq]: \[dq]true\[dq],
\[dq]b\[dq]: true,
\[dq]c\[dq]: \[dq]123\[dq],
\[dq]d\[dq]: 123,
\[dq]e\[dq]: true,
\[dq]f\[dq]: true,
\[dq]g\[dq]: 14,
\[dq]h\[dq]: true
}
$ jo -a -- -s 123 -n \[dq]This is a test\[dq] -b C_Rocks 456
[\[dq]123\[dq],14,true,456]
\f[R]
.fi
.PP
Read element values from files: a value which starts with
\f[C]\[at]\f[R] is read in plain whereas if it begins with a \f[C]%\f[R]
it will be base64-encoded and if it starts with \f[C]:\f[R] the contents
are interpreted as JSON:
.IP
.nf
\f[C]
$ jo program=jo authors=\[at]AUTHORS
{\[dq]program\[dq]:\[dq]jo\[dq],\[dq]authors\[dq]:\[dq]Jan-Piet Mens <jpmens\[at]gmail.com>\[dq]}
$ jo filename=AUTHORS content=%AUTHORS
{\[dq]filename\[dq]:\[dq]AUTHORS\[dq],\[dq]content\[dq]:\[dq]SmFuLVBpZXQgTWVucyA8anBtZW5zQGdtYWlsLmNvbT4K\[dq]}
$ jo nested=:nested.json
{\[dq]nested\[dq]:{\[dq]field1\[dq]:123,\[dq]field2\[dq]:\[dq]abc\[dq]}}
\f[R]
.fi
.PP
These characters can be escaped to avoid interpretation:
.IP
.nf
\f[C]
$ jo name=\[dq]JP Mens\[dq] twitter=\[aq]\[rs]\[at]jpmens\[aq]
{\[dq]name\[dq]:\[dq]JP Mens\[dq],\[dq]twitter\[dq]:\[dq]\[at]jpmens\[dq]}
$ jo char=\[dq] \[dq] URIescape=\[rs]\[rs]%20
{\[dq]char\[dq]:\[dq] \[dq],\[dq]URIescape\[dq]:\[dq]%20\[dq]}
$ jo action=\[dq]split window\[dq] vimcmd=\[dq]\[rs]:split\[dq]
{\[dq]action\[dq]:\[dq]split window\[dq],\[dq]vimcmd\[dq]:\[dq]:split\[dq]}
\f[R]
.fi
.PP
Read element values from a file in order to overcome ARG_MAX limits
during object assignment:
.IP
.nf
\f[C]
$ ls | jo -a > child.json
$ jo files:=child.json
{\[dq]files\[dq]:[\[dq]AUTHORS\[dq],\[dq]COPYING\[dq],\[dq]ChangeLog\[dq] ....
$ ls *.c | jo -a > source.json; ls *.h | jo -a > headers.json
$ jo -a :source.json :headers.json
[[\[dq]base64.c\[dq],\[dq]jo.c\[dq],\[dq]json.c\[dq]],[\[dq]base64.h\[dq],\[dq]json.h\[dq]]]
\f[R]
.fi
.PP
Add elements to existing JSON:
.IP
.nf
\f[C]
$ jo -f source.json 1 | jo -f - 2 3
[\[dq]base64.c\[dq],\[dq]jo.c\[dq],\[dq]json.c\[dq],1,2,3]
$ curl -s \[aq]https://noembed.com/embed?url=https://www.youtube.com/watch?v=dQw4w9WgXcQ\[aq] | jo -f - status=Rickrolled
{ ...., \[dq]type\[dq]:\[dq]video\[dq],\[dq]author_url\[dq]:\[dq]https://www.youtube.com/user/RickAstleyVEVO\[dq],\[dq]status\[dq]:\[dq]Rickrolled\[dq]}
\f[R]
.fi
.PP
Deduplicate object keys (\f[I]jo\f[R] appends duplicate object keys by
default):
.IP
.nf
\f[C]
$ jo a=1 b=2 a=3
{\[dq]a\[dq]:1,\[dq]b\[dq]:2,\[dq]a\[dq]:3}
$ jo -D a=1 b=2 a=3
{\[dq]a\[dq]:3,\[dq]b\[dq]:2}
\f[R]
.fi
.SH OPTIONS
.PP
\f[I]jo\f[R] understands the following global options.
.TP
-a
Interpret the list of \f[I]words\f[R] as array values and produce an
array instead of an object.
.TP
-B
By default, \f[I]jo\f[R] interprets the strings \[lq]\f[C]true\f[R]\[rq]
and \[lq]\f[C]false\f[R]\[rq] as boolean elements \f[C]true\f[R] and
\f[C]false\f[R] respectively, and \[lq]\f[C]null\f[R]\[rq] as
\f[C]null\f[R].
Disable with this option.
.TP
-D
Deduplicate object keys.
.TP
-e
Ignore empty stdin (i.e.\ don\[cq]t produce a diagnostic error when
\f[I]stdin\f[R] is empty)
.TP
-n
Do not add keys with empty values.
.TP
-p
Pretty-print the JSON string on output instead of the terse one-line
output it prints by default.
.TP
-v
Show version and exit.
.TP
-V
Show version as a JSON object and exit.
.SH BUGS
.PP
Probably.
.PP
If a value given to \f[I]jo\f[R] expands to empty in the shell, then
\f[I]jo\f[R] produces a \f[C]null\f[R] in object mode, and might appear
to hang in array mode; it is not hanging, rather it\[cq]s reading
\f[I]stdin\f[R].
This is not a bug.
.PP
Numeric values are converted to numbers which can produce undesired
results.
If you quote a numeric value, \f[I]jo\f[R] will make it a string.
Compare the following:
.IP
.nf
\f[C]
$ jo a=1.0
{\[dq]a\[dq]:1}
$ jo a=\[rs]\[dq]1.0\[rs]\[dq]
{\[dq]a\[dq]:\[dq]1.0\[dq]}
\f[R]
.fi
.PP
Omitting a closing bracket on a nested element causes a diagnostic
message to print, but the output contains garbage anyway.
This was designed thusly.
.SH RETURN CODES
.PP
\f[I]jo\f[R] exits with a code 0 on success and non-zero on failure
after indicating what caused the failure.
.SH AVAILABILITY
.PP
<http://github.com/jpmens/jo>
.SH CREDITS
.IP \[bu] 2
This program uses \f[C]json.[ch]\f[R], by Joseph A.
Adams.
.SH SEE ALSO
.IP \[bu] 2
<https://stedolan.github.io/jq/>
.IP \[bu] 2
<https://github.com/micha/jsawk>
.IP \[bu] 2
<https://github.com/jtopjian/jsed>
.IP \[bu] 2
strtod(3)
.SH AUTHOR
.PP
Jan-Piet Mens <http://jpmens.net>