35

I've been looking for a way to uglify some JSON while in my bash console. This help using it afterward in another command (for example, to pass json inline to httpie)

Giving:

{
    "foo": "lorem",
    "bar": "ipsum"
}

I want to obtain:

{"foo":"lorem","bar":"ipsum"}

NOTE: this question is intentionnaly greatly inspired by it's pretty-print counterpart. However, googling for bash minify json didn't give me a proper result, hence this questions for the minify/uglify.

Ulysse BN
  • 10,116
  • 7
  • 54
  • 82
  • suggestion: add some white-space to the sample strings here. – orion elenzil Aug 30 '23 at 15:56
  • I'm not sure about what you are meaning @orionelenzil, but i you mean strings with spaces inside (`" lorem. "`), I'm not expecting those to be compacted afterward. This question is just an example btw, you'll see that the answer also use an external, way larger json – Ulysse BN Aug 30 '23 at 19:56
  • right, it's a minor thing but with your existing example naively removing whitespace would work fine, but if there were whitespace in the values or keys then such a simple approach would not work, thus motivating the use of something fancier like the suggestions in the answers. – orion elenzil Aug 30 '23 at 20:40
  • Sorry, I don't seem to get what you want... I'd rather keep it simple anyway, this is not a use case, just an example! – Ulysse BN Aug 30 '23 at 20:53

5 Answers5

45

You can use jq -c (compact) option.

jq -c . < input.json

guizo
  • 2,594
  • 19
  • 26
  • Doesn't work if you have a deeply nested JSON file. See: https://stackoverflow.com/questions/75524357/how-to-increase-depth-limit-of-jq – Cornelius Roemer Feb 21 '23 at 18:13
  • In fact, the recursion limit of `jq` seems to be 512, see https://stackoverflow.com/a/75524782/7483211 and based on benchmarks `jj` is much faster! – Cornelius Roemer Feb 21 '23 at 20:35
24

TL;DR

no install

python -c 'import json, sys;json.dump(json.load(sys.stdin), sys.stdout)' < my.json

very fast (with jj)

jj -u < my.json

Perf benchmark

Here's the script, using hyperfine:

#!/usr/bin/env bash

tmp=$(mktemp json.XXX)
tmp_md=$(mktemp md.XXX)

trap "rm $tmp $tmp_md" EXIT

cat <<JSON > $tmp
{
    "foo": "lorem",
    "bar": "ipsum"
}
JSON
hyperfine \
    --export-markdown $tmp_md \
    --warmup 100 \
    "jj -u < $tmp" \
    "yq eval -j -I=0 < $tmp" \
    "xidel -s - -e '\$json' --printed-json-format=compact < $tmp" \
    "jq --compact-output < $tmp" \
    "python3 -c 'import json, sys;json.dump(json.load(sys.stdin), sys.stdout)' < $tmp" \
    "ruby -r json -e 'j JSON.parse \$stdin.read' < $tmp"

pbcopy < $tmp_md

The result on my mac — MacBook Air (M1, 2020), 8 GB:

Command Mean [ms] Min [ms] Max [ms] Relative
jj -u < json.p72 1.3 ± 0.2 0.9 2.7 1.00
yq eval -j -I=0 < json.p72 4.4 ± 0.4 3.8 7.8 3.37 ± 0.65
xidel -s - -e '$json' --printed-json-format=compact < json.p72 5.5 ± 0.3 5.0 6.5 4.19 ± 0.77
python3 -c 'import json, sys;json.dump(json.load(sys.stdin), sys.stdout)' < json.p72 14.0 ± 0.4 13.4 15.0 10.71 ± 1.89
jq --compact-output < json.p72 14.4 ± 2.0 13.2 33.6 11.02 ± 2.45
ruby -r json -e 'j JSON.parse $stdin.read' < json.p72 47.3 ± 0.6 46.1 48.5 36.10 ± 6.32

Result for a large JSON file (14k lines):

http https://france-geojson.gregoiredavid.fr/repo/regions.geojson | jj -p > $tmp
Command Mean [ms] Min [ms] Max [ms] Relative
jj -u < json.wFY 3.4 ± 0.7 2.7 12.2 1.00
jq --compact-output < json.wFY 35.1 ± 0.4 34.5 36.1 10.24 ± 2.23
python3 -c 'import json, sys;json.dump(json.load(sys.stdin), sys.stdout)' < json.wFY 47.4 ± 0.5 46.3 48.7 13.82 ± 3.01
xidel -s - -e '$json' --printed-json-format=compact < json.wFY 55.5 ± 1.2 54.7 63.5 16.17 ± 3.53
ruby -r json -e 'j JSON.parse $stdin.read' < json.wFY 94.9 ± 0.7 93.8 96.8 27.65 ± 6.02
yq eval -j -I=0 < json.wFY 3087.0 ± 26.6 3049.3 3126.8 899.63 ± 195.81

And here is the pretty print counterpart benchmark

Ulysse BN
  • 10,116
  • 7
  • 54
  • 82
  • How about `uglify-js`? – GChuf Apr 10 '20 at 15:33
  • If you are talking about this: http://lisperator.net/uglifyjs/, it is a javascript uglifier, not JSON. And node is less often available than python on computers. Hence I'm not sure it is a good candidate :/ – Ulysse BN Apr 10 '20 at 19:40
  • 1
    Example for the python line: `python -c 'import json, sys;json.dump(json.load(sys.stdin), sys.stdout)' < myfile.json` – Grant Foster Jun 08 '20 at 21:46
  • Ah, very nice. Thank you for testing `xidel`. – Reino Sep 07 '20 at 11:05
  • @UlysseBN Could you include/update results for `xidel`'s latest [development build](https://sourceforge.net/projects/videlibri/files/Xidel/Xidel%20development/)? Lots of improvements. Maybe you can also include `xidel` in your [pretty print post](https://stackoverflow.com/a/61119751/6320039)? – Reino Aug 20 '21 at 22:27
  • @Reino I've updated the script for a clearer one, however the latest `xidel` version on brew is only `0.9.8`. – Ulysse BN Aug 21 '21 at 00:28
  • I have no idea what all these numbers (and i/s for instance) mean, so clearer... not for me. That's a real pitty. v0.9.8 is really old. The recommended binaries to use are those from the development branch. – Reino Aug 21 '21 at 16:17
  • @Reino feel free to [contribute to Homebrew](https://github.com/Homebrew/brew#contributing) to add this build! The `i/s` means iterations per seconds, so how many times the script runs in one second. `i/100ms` is the same on a 100ms scale rather than 1s – Ulysse BN Aug 21 '21 at 18:17
  • `jj` is great, also doesn't throw an error with deeply nested trees. `jq` has a recursion limit of 512. – Cornelius Roemer Feb 21 '23 at 20:34
  • the 'no install' python command-line here produces not-fully-compacted JSON for me. it has spaces after `:` and `,`'s. `jq -c` omits those spaces. – orion elenzil Aug 30 '23 at 16:17
1

yq worked for me, via utilization of input file (containing the prettified JSON)
yq eval -j -I=0 uglify-test.txt
Docs link: https://mikefarah.gitbook.io/yq/usage/convert

straville
  • 986
  • 3
  • 15
  • 21
0

With :

xidel -s input.json -e '$json' --printed-json-format=compact
#or
xidel -s input.json -e 'serialize-json($json)'
{"foo": "lorem", "bar": "ipsum"}

Interesting "benchmark", Ulysse BN.
I couldn't test jj, but on my old cpu these are my results:

var='{
    "foo": "lorem",
    "bar": "ipsum"
}'

time (for i in {1..100}; do python -c 'import json, sys;json.dump(json.load(sys.stdin), sys.stdout)' <<< "$var" >& /dev/null; done)

real    0m10.813s
user    0m7.532s
sys     0m5.798s

time (for i in {1..100}; do jq --compact-output <<< "$var" >& /dev/null; done)

real    0m10.500s
user    0m1.835s
sys     0m0.769s

time (for i in {1..100}; do xidel -se '$json' --printed-json-format=compact <<< "$var" >& /dev/null; done)

real    0m2.250s
user    0m1.692s
sys     0m0.889s
Reino
  • 3,203
  • 1
  • 13
  • 21
0

jq-minify

Here is a bash script that will write back to the file minified

works with bash v3.2+ and jq v1.6+

#!/usr/bin/env bash
set -eu
path=
options=()
# change -c to -r to get pretty-print
set -- "$@" -c .
for arg; do
  if [ -f "$arg" ]; then
    if [ -n "$path" ]; then
      echo "Cannot specify multiple paths to jq-minify" >&2
      exit 1
    fi
    path="$arg"
  else
    options+=("$arg")
  fi
done
tmp=$(mktemp)
jq "${options[@]}" "$path" >"$tmp"
cat "$tmp" >"$path"
Sam Bacha
  • 81
  • 3