Skip to content
jc-rs
GitHub

Decision guide

Native JSON flags or jc-rs?

Prefer a command's documented, stable JSON mode when your supported versions have one. On Linux that means starting with ip -j, lsblk -J, and journalctl -o json. Use jc-rs when formatted text is the real interface you must consume, or when old and mixed systems cannot offer the native mode.

By · Published and checked

QuestionDocumented JSON mode?
YesUse native JSON
NoUse a matching jc-rs parser

In both branches, pin the invocation and test the fields. JSON guarantees syntax, not a permanent application schema.

Use the closest structured interface to the source

Native JSON is produced before a command lays data out for a terminal. It does not depend on translated column headings, padding, tree-drawing characters, or the current screen width. Skipping that presentation layer removes an entire class of parsing failures and one process from the pipeline.

The word native is not enough by itself. Prefer a mode documented by the command, available across the versions you support, and exercised by a schema test. Experimental JSON, an undocumented switch, or fields that change between installed releases still need compatibility work.

1

Documented native JSON

The producer exposes the records and fields you need.

2

Documented machine format

JSON is absent, but the tool offers stable pairs, null delimiters, or another formal mode.

3

jc-rs parser

Human-readable output is unavoidable and a parser exists for that exact command shape.

4

Purpose-built adapter

You own a narrow text contract that no existing parser covers.

Native wins

Use ip -j for addresses and routes

The iproute2 -j option asks the producer for JSON; -p only pretty-prints that JSON for a person. Keep compact output in pipelines and let jq choose the fields.

Interface addresses as a smaller JSON report
ip -j address show |
  jq 'map({
    ifname,
    mtu,
    addresses: [.addr_info[]? | {family, local, prefixlen}]
  })'
Default routes
ip -j route show default |
  jq 'map({gateway, dev, metric: (.metric // 0)})'

Do not pipe ip -j into jc-rs. It is already JSON. For an older host where text from ip route is the only available artifact, use the ip route parser against that text instead.

Native wins

Use lsblk -J, and name every column

lsblk -J returns a JSON object with a blockdevices array. The util-linux manual explicitly warns that default output can change, including defaults selected by convenience options. For a script, JSON plus an implicit column list is only half a contract.

Stable lsblk invocation
lsblk --json --tree \
  --output NAME,TYPE,SIZE,MOUNTPOINTS |
  jq '.blockdevices'

Request --tree when hierarchy matters, and keep NAME in the selected columns. Otherwise the presence of nested children is not a safe assumption. If you must process saved table output from a host without JSON support, the jc-rs lsblk parser converts the familiar columns and adds typed fields such as byte counts.

Native wins

journalctl -o json is already a record stream

Journal entries are structured before they are rendered as short log lines. The json output mode writes one JSON object per line, so it is NDJSON rather than one surrounding array. jq accepts the sequence directly.

Critical SSH service entries from the last hour
journalctl --unit=sshd.service \
  --since='1 hour ago' \
  --no-pager \
  --output=json \
  --output-fields=MESSAGE,PRIORITY,_SYSTEMD_UNIT |
  jq -c 'select((.PRIORITY | tonumber) <= 3)'

Keep it line-delimited for a streaming consumer. To build one bounded array, use jq -s '.' after applying a sensible time or row limit. The distinction is covered in the NDJSON, JSONL, and JSON guide.

Do not render the journal as short output and parse it back into fields. Use the journal's own JSON while you still have access to the source. The syslog parser is for actual syslog records, including exported files and streams. It is not a substitute for journalctl's structured mode.

Where jc-rs is the right boundary

A schema-aware text parser is useful when the text cannot be avoided. That happens more often than a greenfield script suggests: old appliances, support bundles, captured incident output, and commands whose maintainers expose only a terminal format are all common in production environments.

Input you actually haveUseReason
Current ip address or route stateip -j …The producer owns a documented JSON mode.
Current block-device inventorylsblk -J -o …Native JSON plus explicit columns is the strongest contract.
Current systemd journal entriesjournalctl -o jsonIt retains journal fields before display formatting.
Saved legacy lsblk tablejc-rs --lsblkYou cannot rerun the producer, so parse the artifact you have.
systemctl unit-list tablejc-rs --systemctlThe matching parser gives the known table a typed JSON boundary.
ps, df, or ss text from supported systemsmatching jc-rs parserUse one parser per command grammar; do not infer arbitrary columns.
Text-only systemctl pipeline
LC_ALL=C SYSTEMD_COLORS=0 \
  systemctl --all --no-pager --full --plain |
  jc-rs --systemctl |
  jq 'map(select(.active == "failed"))'

The command, options, locale, and parser name together define the input contract. See how to test command-output parsers across locale, width, and version changes.

JSON syntax is not your schema contract

Switching to native JSON prevents column-splitting bugs, but it does not promise that a field will exist forever or keep the same type. Record the producer version, request explicit fields where possible, and assert the minimum shape your consumer needs.

Fail if the lsblk contract changes
lsblk --json --tree --output NAME,TYPE,SIZE,MOUNTPOINTS |
  jq -e '
    select(
      (.blockdevices | type == "array") and
      all(.blockdevices[];
        (.name | type == "string") and
        (.type | type == "string") and
        (.mountpoints | type == "array")
      )
    )
  ' > block-devices.json

Run that assertion in CI against every supported distribution image. When you choose jc-rs, make the same kind of assertion against the fixture-derived schema shown on its parser page. The JSON producer changes, but the consumer still deserves a checked contract.

Upstream references

Flag behavior above is grounded in the upstream manuals: the iproute2 ip(8) manual, util-linux lsblk(8) manual, and systemd journalctl documentation. jc-rs behavior is checked against its repository fixtures.