The problem stems from jq
's possibly surprising default behavior:
keys
enumerates the keys alphabetically sorted.
.[]
enumerates the values based on the keys' input order[1]
In other words: If you use keys
to extract an object's keys in one pass, and then .[]
to extract its values in another, the corresponding output elements might not match.
jq
v1.5 introduced the keys_unsorted/0
function, which enables a simple solution:
# Sample input with unordered keys.
# Sorting the values results in the same order as sorting the keys,
# so the output order of values below implies the key enumeration order that was applied.
json='{ "c":3, "a":1, "b":2 }'
Print keys in input order, using keys_unsorted/0
:
$ echo "$json" | jq -r 'keys_unsorted[]'
c
a
b
Print values in input order, which []
invariably does:
$ echo "$json" | jq -r '.[]'
3
1
2
Caveat: Up to version v1.3, using .[]
resulted in no guaranteed enumeration order (the underlying hash table's key sorting was used, which is an implementation detail); if you still must use v1.3, you can use the to_entries
approach shown below.
[v1.3+] to_entries/0
, as used in user2259432's helpful answer, also enumerates the properties in input order:
# Extract keys
$ echo "$json" | jq -r 'to_entries | map(.key)[]'
c
a
b
# Extract values
$ echo "$json" | jq -r 'to_entries | map(.value)[]'
3
1
2
Caveat: Prior to v1.5, to_entries/0
output key-value pairs in sorted-by-key order.
However, since to_entries/0
can be used to enumerate both keys and values, it is still a viable solution for producing a stable enumeration order in parallel key/value extractions, even in pre-v1.5 versions.
[v1.3+] If, by contrast, you want to enumerate in sorted-by-key order:
Print keys in alphabetically sorted order, using keys/0
:
$ echo "$json" | jq -r 'keys[]'
a
b
c
Print values by alphabetically sorted keys:
$ echo "$json" | jq -r 'keys[] as $k | .[$k]'
1
2
3
A caveat re -S
/ --sort-keys
:
This option only applies to whole objects, on output:
$ echo "$json" | jq -Sc '.'
{"a":1,"b":2,"c":3} # Sorted by key
It doesn't apply when you use an operator or function to access the internals of an object:
$ echo "$json" | jq -S '.[]' # !! -S doesn't apply, because [] always uses input order
3
1
2
[1] Prior to v1.5, no particular order was guaranteed, resulting the same problem, however.