JT(1)
okay so bestie, welcome to jt(1) from the JT MANUAL โ the page for jt, the tool that transforms JSON data into tabular format. Buckle up, because this is stack-based and kinda iconic ๐
โจ
๐ธ NAME ๐ธ
jt โ transform JSON data to tabular format. That's literally the whole assignment: JSON in, table out ๐ซถ
๐ SYNOPSIS ๐
jt [-hV]
jt -u string
jt [-acj] [COMMAND ...]
๐ฆ DESCRIPTION ๐ฆ
jt reads UTF-8 encoded JSON forms from stdin and writes tab separated values (or CSV) to stdout. A simple stack-based programming language is used to extract values from the JSON input for printing. So yeah: JSON comes in, your little command stack does its thing, and a table comes out. No cap ๐
โจ OPTIONS โจ
-hโ Print usage info and exit.-Vโ Print version and license info and exit.-aโ Explicit iteration mode: require the.command to iterate over arrays instead of iterating automatically.-cโ CSV output mode: write RFC 4180 compliant CSV records.-jโ Inner join mode: discard rows with missing columns.-sโ A no-op, included for compatibility with earlier versions.-u stringโ Unescape JSON string, print it, and exit. Double-quotes around string are optional.
-s is literally a no-op, bestie โ it's there for compatibility with earlier versions, not because it does anything.
๐ OPERATION ๐
Non-option arguments are words (commands) in a stack-based programming language. Commands operate on the stacks provided by the jt runtime:
- The DATA stack contains JSON values commands will operate on.
- The GOSUB stack contains pointers into the data stack.
- The INDEX stack contains pointers to iterators during iteration.
- The LOOP stack contains pointers to iterators between iterations.
- The OUTPUT stack contains values to print after each iteration.
When jt starts, the following happens:
- One JSON form is read from stdin, parsed, and pushed onto the data stack.
- If the top of the data stack is a JSON array the next item in the array is pushed onto the data stack based on state previously saved on the loop stack. The loop stack is then popped and pushed onto the index stack.
- The next command is executed.
- Steps 2 and 3 are repeated until no commands are left to execute.
- Values in the output stack are printed, separated by tabs and followed by a newline (see OUTPUT FORMAT below). Data, output, and gosub stacks are reset to their states as they were after step 1.
- Loop variables are popped off of the index stack. They are then incremented and pushed onto the loop stack as necessary.
- Steps 2 through 6 are repeated until the loop stack is empty (i.e. there are no more iterations left).
This process is repeated until there is no more JSON to read.
๐ฅ COMMANDS ๐ฅ
jt provides the following commands:
[โ Save the state of the data stack: the current data stack pointer is pushed onto the gosub stack.]โ Restore the data stack to a saved state: pop the gosub stack and restore the data stack pointer to that state.%โ Push the value at the top of the data stack onto the output stack.%=NAMEโ Like%, but also sets the heading for this column toNAME. Headers will be printed as the first line of output when this command has been used.^โ Push the value at the top of the index stack onto the output stack. Note that the index stack will always have at least one item: the index of the last JSON object read from stdin.^=NAMEโ Like^, but also sets the heading for this column toNAME. Headers will be printed as the first line of output when this command has been used.@โ Print the keys of the object at the top of the data stack and exit.- If the value at the top of the data stack is an array the first item in the array is pushed onto the data stack and the command is reevaluated.
- If the value at the top of the data stack is not an object or an array then its type is printed in brackets, eg.
[array],[string],[number],[boolean], or[null]. If there is no value then[none]is printed.
+โ Parse embedded JSON: if the item at the top of the data stack is a string, parse it and push the result onto the data stack.- If the item at the top of the data stack is not a string or if there is an error parsing the JSON then nothing is done (the operation is a no-op).
.โ Iterate over the values of the object at the top of the data stack. The current value will be pushed onto the data stack and the current key will be pushed onto the index stack.[KEY]โ Drill down: get the value of theKEYproperty of the object at the top of the data stack and push that value onto the data stack.- If the item at the top of the data stack is not an object or if the object has no
KEYproperty a blank field is printed, unless the-joption was specified in which case the entire row is removed from the output. - If the
KEYproperty of the object is an array subsequent commands will operate on one of the items in the array, chosen automatically byjt. The array index will be available to subsequent commands via the index stack.
- If the item at the top of the data stack is not an object or if the object has no
KEYโ See[KEY]above โ the[and]may be omitted if the property nameKEYdoes not conflict with anyjtcommand.
๐ OUTPUT FORMAT ๐
The format of printed output from jt (see % in COMMANDS, above) depends on the type of thing being printed:
๐ Primitives ๐
JSON primitives (i.e. null, true, and false) and numbers are printed verbatim. jt does not process them in any way. Numbers can be of arbitrary precision, as long as they conform to the JSON parsing grammar.
If special formatting is required the printf program is your friend:
$ printf %.0f 2.99792458e9
2997924580
๐ Strings ๐
Strings are printed verbatim, minus the enclosing double quotes. No unescaping is performed because tabs or newlines in JSON strings would break the tabular output format.
If unescaped values are desired either use the -c flag to enable CSV output (see CSV below) or use the -u option and pass the string as an argument:
$ jt -u ยดi love music \u266Aยด
i love music โช
๐ฆ Collections ๐ฆ
Objects and arrays are printed as JSON with whitespace removed. Note that it is only possible to print arrays when the -a option is specified (see the Iteration (Arrays) and Explicit Iteration sections below).
๐ CSV ๐
The -c flag enables CSV output conforming to RFC 4180. This format supports strings containing tabs, newlines, etc.:
$ jt -c [ foo % ] [ bar % ] baz % <<EOT
- {
- "foo": "one\ttwo",
- "bar": "three\nfour",
- "baz": "music \"\u266A\""
- }
- EOT
"one two","three
four","music ""โช"""
๐ EXIT STATUS ๐
jt will exit with a status of 1 if an error occurred, or 0 otherwise.
๐ธ EXAMPLES ๐ธ
okay bestie this is the fun part โ a whole bunch of tiny worked examples showing you how to poke around JSON and JSON streams with jt and actually pull stuff out. explore, extract, iterate, joinโฆ it's giving a whole curriculum fr fr ๐
๐ฆ Explore
The @ command prints info about whatever's sitting on top of the data stack. When that thing is an object, @ spills out its keys:
$ jt @ <<EOT
- {
- "foo": 100,
- "bar": 200,
- "baz": 300
- }
- EOT
foo
bar
baz
When the top item is an array, @ gives you info about the first item in the array:
$ jt @ <<EOT
- [
- {"foo": 100, "bar": 200},
- {"baz": 300, "baf": 400}
- ]
- EOT
foo
bar
Otherwise @ just prints the type of the item โ or [none] if there's literally no value at all:
$ echo ยด["hello", "world"]ยด | jt @
[string]
$ echo ยด[1, 2]ยด | jt @
[number]
$ echo ยด[true, false]ยด | jt @
[boolean]
$ echo ยด[null, null]ยด | jt @
[null]
$ echo ยด[]ยด | jt @
[none]
๐ซถ Drill Down
Property names are also commands, iconic behaviour honestly. Use foo as a command to drill down into the foo property, then @ to print its keys:
$ jt foo @ <<EOT
- {
- "foo": {
- "bar": 100,
- "baz": 200
- }
- }
- EOT
bar
baz
Now, if a property name happens to clash with one of jt's own commands, just wrap it in square brackets to drill down anyway:
$ jt [@] @ <<EOT
- {
- "@": {
- "bar": 100,
- "baz": 200
- }
- }
- EOT
bar
baz
๐ก Note: the property name has to be a JSON escaped string. So if the property name in your JSON input contains something like tab characters, you gotta escape them in the command too, like this:
$ jt ยดfoo\tbarยด @ <<EOT
- {
- "foo\tbar": {
- "bar": 100,
- "baz": 200
- }
- }
- EOT
bar
baz
โจ Extract
The % command prints the item on top of the data stack. Heads up though โ when the top item is a collection, it gets printed as JSON (with that insiginificant whitespace removed):
$ jt % <<EOT
- {
- "foo": 100,
- "bar": 200
- }
- EOT
{"foo":100,"bar":200}
Drill down, then print:
$ jt foo bar % <<EOT
- {
- "foo": {
- "bar": 100
- }
- }
- EOT
100
And you can absolutely use % more than once โ the printed values come out separated by tabs:
$ jt % foo % bar % <<EOT
- {
- "foo": {
- "bar": 100
- }
- }
- EOT
{"foo":{"bar":100}} {"bar":100} 100
๐ Save / Restore
The [ and ] commands are your little save points ๐ฎ โ they let you stash the data stack's state before doing some operations, then restore it afterwards. Which means you can drill down into one part of an object, "rewind", and then drill down into a whole other part of the same object. main character energy tbh.
[ saves the state of the data stack, and ] restores the data stack back to the matching saved state.
โ ๏ธ It is an error to use the]command without a corresponding[command. no cap, don't do it.
$ jt [ foo % ] bar % <<EOT
- {
- "foo": 100,
- "bar": 200
- }
- EOT
100 200
And yeah, [ and ] nest like a dream:
$ jt [ foo [ bar % ] [ baz % ] ] baf % <<EOT
- {
- "foo": {
- "bar": 100,
- "baz": 200
- },
- "baf": "quux"
- }
- EOT
100 200 quux
๐ Iteration (Arrays)
jt automatically iterates over arrays (unless you've disabled that behaviour โ see Explicit Iteration below ๐), producing one tab-delimited record per iteration, with records separated by newlines:
$ jt [ foo % ] bar baz % <<EOT
- {
- "foo": 100,
- "bar": [
- {"baz": 200},
- {"baz": 300},
- {"baz": 400}
- ]
- }
- EOT
100 200
100 300
100 400
The ^ command sneaks the array index in as a column of the result:
$ jt [ foo % ] bar ^ baz % <<EOT
- {
- "foo": 100,
- "bar": [
- {"baz": 200},
- {"baz": 300},
- {"baz": 400}
- ]
- }
- EOT
100 0 200
100 1 300
100 2 400
And ^ is scoped to the innermost enclosing loop โ so it knows exactly which loop it belongs to:
$ jt foo ^ bar ^ % <<EOT
- {
- "foo": [
- {"bar": [100, 200]},
- {"bar": [300, 400]}
- ]
- }
- EOT
0 0 100
0 1 200
1 0 300
1 1 400
๐ Iteration (Objects)
The . command iterates over the values of an object:
$ jt . % <<EOT
- {
- "foo": 100,
- "bar": 200,
- "baz": 300
- }
- EOT
100
200
300
And when you're iterating over an object, ^ prints the name of the current property instead:
$ jt . ^ % <<EOT
- {
- "foo": 100,
- "bar": 200,
- "baz": 300
- }
- EOT
foo 100
bar 200
baz 300
๐ JSON Streams
jt automatically iterates over the entities in a JSON stream (optionally delimited by whitespace):
$ jt [ foo % ] bar % <<EOT
- {"foo": 100, "bar": 200}
- {"foo": 200, "bar": 300}
- {"foo": 300, "bar": 400}
- EOT
100 200
200 300
300 400
And ^ prints the current stream index:
$ jt ^ [ foo % ] bar % <<EOT
- {"foo": 100, "bar": 200}
- {"foo": 200, "bar": 300}
- {"foo": 300, "bar": 400}
- EOT
0 100 200
1 200 300
2 300 400
๐ง Nested JSON
The + command parses JSON that's been embedded inside strings โ yes, JSON in your JSON, we love a recursive moment:
$ jt [ foo + bar % ] baz % <<EOT
- {"foo":"{\"bar\":100}","baz":200}
- {"foo":"{\"bar\":200}","baz":300}
- {"foo":"{\"bar\":300}","baz":400}
- EOT
100 200
200 300
300 400
๐ก Note: the + command does not modify the original JSON:
$ jt [ foo + bar % ] % <<EOT
- {"foo":"{\"bar\":100}","baz":200}
- {"foo":"{\"bar\":200}","baz":300}
- {"foo":"{\"bar\":300}","baz":400}
- EOT
100 {"foo":"{\"bar\":100}","baz":200}
200 {"foo":"{\"bar\":200}","baz":300}
300 {"foo":"{\"bar\":300}","baz":400}
๐ Column Headings
%=NAME and ^=NAME are exactly like % and ^ above, except they also assign column headings โ which get printed as the very first line of output. slay:
$ jt [ foo %=Foo ] bar ^=Bar baz %=Baz <<EOT
- {
- "foo": 100,
- "bar": [
- {"baz": 200},
- {"baz": 300},
- {"baz": 400}
- ]
- }
- EOT
Foo Bar Baz
100 0 200
100 1 300
100 2 400
๐ก Note: the headingNAMEmust be a JSON escaped string โ so if you want a column heading with embedded tab characters, use%=foo\tbarinstead of"%=foo bar".
๐ฅ Joins
First, peep the empty column โ some of these objects just don't have the bar key, and that's fine:
$ jt [ foo % ] bar % <<EOT
- {"foo":100,"bar":1000}
- {"foo":200}
- {"foo":300,"bar":3000}
- EOT
100 1000
200
300 3000
Now turn on inner join mode with the -j flag. This drops output rows whenever a key in the traversal path doesn't exist:
$ jt -j [ foo % ] bar % <<EOT
- {"foo":100,"bar":1000}
- {"foo":200}
- {"foo":300,"bar":3000}
- EOT
100 1000
300 3000
๐ Note that this does not remove rows when the key exists and the value is just empty:
$ jt -j [ foo % ] bar % <<EOT
- {"foo":100,"bar":1000}
- {"foo":200,"bar":""}
- {"foo":300,"bar":3000}
- EOT
100 1000
200
300 3000
๐ Explicit Iteration
Sometimes that implicit iteration over arrays is justโฆ awkward. like, bestie, read the room.
$ jt . ^ . ^ % <<EOT
- {
- "foo": [
- {"bar":100},
- {"bar":200}
- ]
- }
- EOT
0 bar 100
1 bar 200
So the question is: should that first ^ be printing the array index of the implicit iteration (which, in this case, it is), or the object key (i.e. foo) of the explicit iteration of the . command? the ambiguity is loud ngl.
Another awkward case is printing arrays:
$ jt foo % <<EOT
- {
- "foo": [
- {"bar":100},
- {"bar":200}
- ]
- }
- EOT
{"bar":100}
{"bar":200}
The array can't be printed with the % command because it's being iterated over implicitly. Instead the items in the array get printed, which may not be what you actually wanted. ๐
The -a flag eliminates the ambiguity by enabling explicit iteration. In this mode you must use the . command to iterate over both objects and arrays โ arrays do not get automatically iterated over.
Now the array can be printed:
$ jt -a foo % <<EOT
- {
- "foo": [
- {"bar":100},
- {"bar":200}
- ]
- }
- EOT
[{"bar":100},{"bar":200}]
Or the first ^ can print the array index, same as before:
$ jt -a . . ^ . ^ % <<EOT
- {
- "foo": [
- {"bar":100},
- {"bar":200}
- ]
- }
- EOT
0 bar 100
1 bar 200
Or it can print the object key:
$ jt -a . ^ . . ^ % <<EOT
- {
- "foo": [
- {"bar":100},
- {"bar":200}
- ]
- }
- EOT
foo bar 100
foo bar 200
Or, with just one more ^ command added, it can print both:
$ jt -a . ^ . ^ . ^ % <<EOT
- {
- "foo": [
- {"bar":100},
- {"bar":200}
- ]
- }
- EOT
foo 0 bar 100
foo 1 bar 200
๐ซ SEE ALSO ๐ซ
jt is based on ideas from the excellent jshon tool, which you can find at http://kmkeen.com/jshon/. Source code for jt lives at https://github.com/micha/json-table. go be nosy, iykyk ๐ซถ
๐ BUGS ๐
Please open an issue right here: https://github.com/micha/json-table/issues. do it, the maintainers wanna hear from you ๐
๐ COPYRIGHT ๐
okay bestie, legal era ๐ this is the part where we give credit where credit is due, and honestly it's kind of a slay.
Copyright ยฉ 2017 Micha Niskin <micha.niskin@gmail.com>, distributed under the Eclipse Public License, version 1.0. ๐ฆ
so like, this is free software โ and no, "free" here means freedom, not free-as-in-someone-bought-you-a-strawberry-matcha ๐ you are free to change it and redistribute it, go absolutely off, remix the whole thing, that's literally the point. periodt.
โ ๏ธ but hear me on this one: there is NO WARRANTY, to the extent permitted by law. like none. zero. nada. if it breaks, that's between you and the void ๐ญ it's giving "no cap, you're on your own" โ but like, lovingly. ๐ซถ
and that's a wrap on JT(1), bestie!! ๐โจ you read the whole thing, which honestly is main character energy of the highest order ๐ this page is from July 2017, and the source/version line at the bottom literally just says JT(1) โ so yeah, that's the whole flex. go forth, run your commands, and remember: read the manpage first, slay always ๐๐โญ
๐ฆ where this page comes from
- opensuse-leap 16.0: json-table 4.3.3-bp160.1.12 ยท EPL-1.0
- opensuse-leap 16.0: json-table 4.3.3-bp160.1.12 ยท EPL-1.0
- opensuse-leap 16.0: json-table 4.3.3-bp160.1.12 ยท EPL-1.0
- opensuse-leap 16.0: json-table 4.3.3-bp160.1.12 ยท EPL-1.0
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms