3

I recently discovered jq and gron. For my use case, gron is sufficient, but I am a little worried by lack of development. There are some bugs that have not been addressed in an year, while I see that jq is actively developed.

So I would like to emulate gron using jq. I suspect it is not too difficult, but my knowledge of jq is limited.

As an example, I'd like to use jq to obtain this output:

T424f7496c3a01  -14889.86
demo    -15030.785

from this input:

{
  "T424f7496c3a01": {
    "remaining": -14889.86,
    "ts": 136572.504
  },
  "demo": {
    "remaining": -15030.785,
    "ts": 0.515
  }
}

I have come up with this jq script which does it:

jq -r '[keys,([.[]|.remaining|round|"\t"+tostring])]|transpose|.[]|add'

but it is much less readable than

gron | fgrep '.remaining = '

which produces

json.T424f7496c3a01.remaining = -18079.105;
json.demo.remaining = -18220.029;

that can be easily parsed using an IFS=" .=;" read loop in bash.

So I am asking a question of long-term maintainability: is there a simple script for jq that can give me something easily parseable in bash? I fear that using the jq script above in one month I will have no idea how it works...

P.S. The answer I chose answers the above query. I hoped there would be an equally simple answer for the query mentioned in the title, i.e. emulating gron by using jq. In fact, the comment below by @Kusalananda references How to print path and key values of JSON file, where the chosen answer is near enough to a gron emulation. Unfortunately, that solution is too complex to satisfy my long-term maintenance and readablity objectives.

1
  • 2
    Although it's out of scope for this question... whatever you were going to do with that data after parsing it in bash, you might also be able to do in jq. If so, you wouldn't even need to convert it into tab-separated values. Commented Oct 3, 2024 at 8:59

2 Answers 2

10

Convert the input object to a set of "entries" with to_entries. Each entry is an object with a key key (the key from the object) and a value key (the value of that key in the object).

Pick out the .key and the corresponding .value.remaining value from each entry and put them in an array that you pass through @tsv to output a tab-delimited list.

$ jq -r 'to_entries[] | [.key, .value.remaining] | @tsv' file
T424f7496c3a01  -14889.86
demo    -15030.785

You could read this with IFS=$'\t' read -r the_id remaining in a loop, if you wanted, assuming no keys contained literal tabs, or newlines.


With intermediate results displayed:

$ jq -r 'to_entries[]' file
{
  "key": "T424f7496c3a01",
  "value": {
    "remaining": -14889.86,
    "ts": 136572.504
  }
}
{
  "key": "demo",
  "value": {
    "remaining": -15030.785,
    "ts": 0.515
  }
}
$ jq -r 'to_entries[] | [.key, .value.remaining]' file
[
  "T424f7496c3a01",
  -14889.86
]
[
  "demo",
  -15030.785
]
$ jq -r 'to_entries[] | [.key, .value.remaining] | @tsv' file
T424f7496c3a01  -14889.86
demo    -15030.785
5
  • That's a nice answer and I have used it in my script, thanks. It is more readable than the solution that I had found. I would ask, is there a (simple) way to do what I ask in the title of my question above, that is, emulating gron by using jq? Commented Oct 3, 2024 at 9:11
  • 1
    @FrancescoPotortì I don't know gron at all so I don't know how to emulate it fully. However, if all you want is the "path" together with each leaf value, then you may use jq. There is at least one question with answers about this on this site: unix.stackexchange.com/questions/561460/… Commented Oct 3, 2024 at 9:19
  • @FrancescoPotortì My answer here reproduces the output you wanted from the input you gave, so that was the issue I solved. Commented Oct 3, 2024 at 9:22
  • Thanks, I looked at the question you cited and experimented with the chosen answer. It mostly answers the query in my title. Not an exact emulation of gron's output, but similar enough. However, it is too complex to be advantageous in light of long-term maintenance. I'll go with your solution. Commented Oct 3, 2024 at 10:07
  • See also jq -c 'to_entries[]|{"key"}+.value' | mlr --j2t cat to get a TSV with headers (or mlr --j2p --barred cat for some pretty printed table, or vd -f json for an interactive one) Commented Oct 23, 2024 at 15:00
4

Cheap gron emulator:

jq -r 'paths(scalars) as $key | "\($key | join(".")) = \(getpath($key))"'

For your data that will output

T424f7496c3a01.remaining = -14889.86
T424f7496c3a01.ts = 136572.504
demo.remaining = -15030.785
demo.ts = 0.515

The output isn't quite the same:

  • The initial json. is left out (you can have it back if you really want).
  • The ending semicolon is left out (ditto).
  • Arrays will be output as foo.0, foo.1, etc. instead of foo[0] and foo[1] (otherwise formatting would be more complicated than just join).
  • The "empty" = {} or = [] lines declaring objects and arrays are left out (it would just take a little handling to detect the type of each value).

But in general it's similar and you can grep it in the same way that you do with the gron output.

Overall I think it's more productive to learn how to solve problems directly in jq (you can do more interesting things and get output in exactly the format you want), but this is something you can stick in an alias and use like you would use gron.

2
  • Very nice, thanks. This is what I was looking for in the beginning. In fact, I'll follow your advice and go with jq using @Kusalananda's answer as a template. And I will probably use your answer sometime in the future, and now as a learning exercise. Which is not so basic as it uses variables, which the jq manual describes as "advanced" :) Commented Oct 5, 2024 at 15:38
  • 1
    See also stackoverflow.com/a/79115403/152948 which does pretty much the same thing but handles array indexing using square brackets. Commented Oct 23, 2024 at 14:38

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.