use jq to extract ranking from a list - json

I want to extract a ranking from a list of elements inside a json object whose order shows the position of the item in the respective category. That should be done by using only command line tools.
E.g.
I have a file with these 2 samples (each json object should be one-line but parsed with indentation here for readability ) :
{
"category":"triathlon",
"athletes_list":[
{
"athlete_name": "Ubain Solt"
}
,
{
"athlete_name": "Jon Snow"
}
,
{
"athlete_name": "Mickey Mouse"
}
]
}
{
"category":"swimming",
"athletes_list":[
{
"athlete_name": "Picheal Phelms"
},
{
"athlete_name":"Lacky Kedetie"
}
]
}
and want as output:
{"a":"Ubain Solt", "r":0, "c":"triathlon"}
{"a":"Jon Snow", "r":1, "c":"triathlon"}
{"a": "Mickey Mouse", "r":2, "c":"triathlon"}
{"a": "Picheal Phelms", "r":0, "c":"swimming"}
{"a": "Lacky Kedetie", "r":1, "c":"swimming"}
Requirements are to use jq and solution must be 1-liner.

jq -s '.[] | .category as $c | .athletes_list | to_entries[] | ({ a: .value.athlete_name, r: .key, c: $c })' triathlon swimming
Will produce:
{
"a": "Ubain Solt",
"r": 0,
"c": "triathlon"
}
{
"a": "Jon Snow",
"r": 1,
"c": "triathlon"
}
{
"a": "Mickey Mouse",
"r": 2,
"c": "triathlon"
}
{
"a": "Picheal Phelms",
"r": 0,
"c": "swimming"
}
{
"a": "Lacky Kedetie",
"r": 1,
"c": "swimming"
}
The 'trick' is to use to_entries so we can use .key as the 'index'.

Related

How to insert a field at the top of an object?

This is my code
element='{"x":"zero"}'
example='[
{
"a":"one",
"b":"two",
"c":"three"
},
{
"d":"four",
"e":"five",
"f":"six"
}]'
jq --argjson el "$element" '.[] += $el' <<< $example
It currently outputs
[
{
"a":"one",
"b":"two",
"c":"three",
"x":"zero"
},
{
"d":"four",
"e":"five",
"f":"six",
"x":"zero"
}
]
But i would like to have the "x":"zero" be the first element of all array elements, not the last.
Inverting the variable with the .[] iterator would make no sense. Any idea how this can be done?
In order for the new field to appear at the top, the object that holds it must appear first in the addition statement. For example:
$ jq --argjson el "$element" 'map($el + .)' <<< $example
[
{
"x": "zero",
"a": "one",
"b": "two",
"c": "three"
},
{
"x": "zero",
"d": "four",
"e": "five",
"f": "six"
}
]

Reverse flatten nested arrays with jq

Suppose I have the following nested data structure
cat nested.json
[
{
"a": "a",
"b": [
{"c": "c"}
]
},
{
"a": "a",
"b": [
{"c": "c"}
]
}
]
I can flatten it like this
cat nested.json | jq '
[. as $in | reduce paths(scalars) as $path ({};
. + { ($path | map(tostring) | join(".")): $in | getpath($path) }
)]
' > flat.json
cat flat.json
[
{
"0.a": "a",
"0.b.0.c": "c",
"1.a": "a",
"1.b.0.c": "c"
}
]
To reverse the flatten operation with jq I tried this
cat flat.json | jq '
.[0] | reduce to_entries[] as $kv ({};
setpath($kv.key|split("."); $kv.value)
)
'
{
"0": {
"a": "a",
"b": {
"0": {
"c": "c"
}
}
},
"1": {
"a": "a",
"b": {
"0": {
"c": "c"
}
}
}
}
However, I want to convert numbers in the setpath param to create arrays. This doesn't quite work, but I think it's close?
cat flat.json | jq '
def makePath($s): [split(".")[] | if (test("\\d+")) then tonumber else . end];
.[0] | reduce to_entries[] as $kv ({}; setpath(makePath($kv.key); $kv.value))
'
jq: error (at <stdin>:8): split input and separator must be strings
The desired output is the same as the original data in nested.json
Wouldn't it be simpler to do it this way:
Encode your input with
jq '[path(.. | scalars) as $path | {($path | join(".")): getpath($path)}] | add' nested.json
{
"0.a": "a",
"0.b.0.c": "c",
"1.a": "a",
"1.b.0.c": "c"
}
And decode it with
jq 'reduce to_entries[] as $item (null; setpath($item.key / "." | map(tonumber? // .); $item.value))' flat.json
[
{
"a": "a",
"b": [
{
"c": "c"
}
]
},
{
"a": "a",
"b": [
{
"c": "c"
}
]
}
]
However, if you don't care about your special dot notation (e.g. "0.b.0.c") for the encoded keys, you can simply convert the path array into a JSON string instead, having albeit uglier virtually the same effect. Moreover, it would automatically enable the handling of input object field names that include dots (e.g. {"a.b":3}) or look like numbers (e.g. {"42":"Panic!"}).
Using JSON keys, encode your input with
jq '[path(.. | scalars) as $path | {($path | tojson): getpath($path)}] | add' nested.json
{
"[0,\"a\"]": "a",
"[0,\"b\",0,\"c\"]": "c",
"[1,\"a\"]": "a",
"[1,\"b\",0,\"c\"]": "c"
}
And decode it with
jq 'reduce to_entries[] as $item (null; setpath($item.key | fromjson; $item.value))' flat.json
[
{
"a": "a",
"b": [
{
"c": "c"
}
]
},
{
"a": "a",
"b": [
{
"c": "c"
}
]
}
]

how to use jq to filter an object based on it's key:value pair?

Assuming this is the input
>echo '{"A": {"x": 1}, "B": {"x":2}, "C":{"x":3}}' | jq '.'
{
"A": {
"x": 1
},
"B": {
"x": 2
},
"C": {
"x": 3
}
}
I would like to get the return object of "x" == 2.
The only way i know of how to achieve that is thru this
>echo '{"A": {"x": 1}, "B": {"x":2}, "C":{"x":3}}' | jq '.[] | select(.x==2)'
{
"x": 2
}
Is there a way to have jq return me like this instead?
{
"B": {
"x": 2
},
}
You can also use map_values :
map_values(select(.x == 2))
with_entries(select(.value.x == 2))

Conditional deletion from array of field A with condition on field B

Let's say I have a json with an array inside. Say that the elements of this array are objects with keys A and B. I would like to remove the B objects on the elements where A objects meet a certain condition.
For example, I would like to remove the B objects where A is greater than 5, transforming
{
"title": "myTitle",
"myArray": [
{
"A": 1,
"B": "foo"
},
{
"A": 4,
"B": "bar"
},
{
"A": 7,
"B": "barfoo"
},
{
"A": 9,
"B": "foobar"
}
]
}
into
{
"title": "myTitle",
"myArray": [
{
"A": 1,
"B": "foo"
},
{
"A": 4,
"B": "bar"
},
{
"A": 7
},
{
"A": 9
}
]
}
The task seems easy enough and if I had't have to keep the A's it would be a simple del(select..) thing. There surely must be an elegant way to do this as well?
Thank you!
You can still use a del(select..) thing.
.myArray[] |= del(select(.A > 5) .B)
demo at jqplay.org

How can you sort a JSON object efficiently using JQ

I've got JSON in the format
{
"a": {
"size":3
},
"b": {
"size":2
},
"c": {
"size":1
}
}
I need to sort it by size, e.g.:
{
"c": {
"size": 1
},
"b": {
"size": 2
},
"a": {
"size": 3
}
}
I have found a way to do it, e.g.:
. as $in | keys_unsorted | map ({"key": ., "size" : $in[.].size}) | sort_by(.size) | map(.key | {(.) : $in[.]}) | add
but this seems quite complex so I'm hoping there's a simpler way that I've overlooked?
You can use to_entries / from_entries, like this:
jq 'to_entries|sort_by(.value.size)|from_entries' file.json
to_entries will transform your input object into a list of key/value pair objects:
[
{
"key": "a",
"value": {
"size": 3
}
},
...
{
"key": "c",
"value": {
"size": 1
}
}
]
That allows to apply sort_by(.value.size) to that list and then convert it back to an object using from_entries.