Why do we need map function in jq? - jq

I am just playing with jq. Please check the following outputs.
% FRUITS='[
{
"name": "apple",
"color": "green",
"price": 1.2
},
{
"name": "banana",
"color": "yellow",
"price": 0.5
},
{
"name": "kiwi",
"color": "green",
"price": 1.25
}
]'
% echo $FRUITS
% echo $FRUITS | jq .
% echo $FRUITS | jq '[.[].color] | unique'
[
"green",
"yellow"
]
% echo $FRUITS | jq 'map(.color) | unique'
[
"green",
"yellow"
]
% echo $FRUITS | jq '. | map(has("name"))'
[
true,
true,
true
]
% echo $FRUITS | jq '[.[] | has("name")]'
[
true,
true,
true
]
Here, jq '[.[].color] | unique' give the same output as jq 'map(.color) | unique'
jq '[.[] | has("name")]' give the same output as jq '. | map(has("name"))'
I am not understanding what is the purpose and difference of the map function.

There's absolutely no difference between [ .[] | ... ] vs map( ... ). You can see this in jq's source.
Languages often provide syntactical shortcuts to make certain tasks simpler than by using general tools. This is known as syntactic sugar.
While map is not a special syntax, the same concept applies. It's a nice way to do something that's commonly needed.
It also imports a well-known concept to jq. While map originated in functional programming languages, it has been adopted by a very large number of languages (sometimes under a different name).
Lisp: maplist, mapcar
Perl: map
JavaScript: Array.prototype.map
Python: map
C#: Enumerable.Select
C++: std::transform
etc

Related

jq: filter out IP addresses by regular expression

[
{
"arguments": {
"leases": [
{
"cltt": 1658763299,
"fqdn-fwd": false,
"fqdn-rev": false,
"hostname": "",
"hw-address": "00:aa:bb:cc:dd:ee",
"ip-address": "192.168.0.2",
"state": 0,
"subnet-id": 1,
"valid-lft": 3600
},
{
"cltt": 1658763207,
"fqdn-fwd": false,
"fqdn-rev": false,
"hostname": "",
"hw-address": "00:11:22:33:44:55",
"ip-address": "192.168.1.3",
"state": 0,
"subnet-id": 1,
"valid-lft": 3600
}
]
},
"result": 0,
"text": "2 IPv4 lease(s) found."
}
]
This is a snippet, but in reality there's much more entries. Currently I filter out MAC and IP with jq expression:
jq --raw-output '.[0] | select(.result == 0) | .arguments.leases[] | "\(.["hw-address"]) \(.["ip-address"])"'
Now I'm wondering: does jq have ability to filter out by regexp? For instance I'd like to dump only entries where IP is 192.168.1.*, can it be done with jq? Ideally I'd like to pass regexp to my script as a parameter:
jq --raw-output --arg addr "$1" ...
Would appreciate suggestions on how to do this.
jq has test to test an input against a regular expression:
first
| select(.result == 0)
| .arguments.leases[]
| select(."ip-address"|test("^192\\.168\\.1"))
| "\(."hw-address") \(."ip-address")"
and to provide the regex as argument via command line:
jq -r --arg regex '^192\.168\.1\.' 'first
| select(.result == 0)
| .arguments.leases[]
| select(."ip-address"|test($regex))
| "\(."hw-address") \(."ip-address")"'
If you only want to check the start of the IP address, you could also use startswith: select(."ip-address"|startswith("192.168.1.")):
jq -r --arg prefix '192.168.1.' 'first
| select(.result == 0)
| .arguments.leases[]
| select(."ip-address"|startswith($prefix))
| "\(."hw-address") \(."ip-address")"'
You can use test with regular expressions, and select to filter:
jq -r --arg addr "192\\.168\\.1\\..*" '
.[0] | select(.result == 0) | .arguments.leases[]
| "\(.["hw-address"]) \(.["ip-address"] | select(test($addr)))"
'
00:11:22:33:44:55 192.168.1.3
Demo
Note: 192.168.1.* is not a regular expression (or at least not one you want to use, as it would also match 192.168.100.4 for instance, because . stands for any value; a literal dot has to be escaped)

using jq how to sort array of object

There is an array of json object, using jq how to check if an object exists if so returns the true else false
I tried this but getting error
cat fruits.json | jq '.fruits[]| sort_by(.version)'
I would like to sort by decending order and output the price of the most recent version.
{
"fruits": [
{
"name": "banana",
"color": "yellow",
"price": 0.51,
"version": 1
},
{
"name": "banana",
"color": "yellow",
"price": 0.52,
"version": 2
}
]
}
cat fruits.json | jq '.fruits | sort_by(-.version)[0].price'
produces:
0.52
You need try like:
.fruits |= sort_by(.version)
Example: https://jqplay.org/s/ntjioYhKWq
Reference sort by keys

Extract nested properties from an array of objects

I have the following JSON file :
{
"filter": [
{
"id": "id_1",
"criteria": {
"from": "mail#domain1.com",
"subject": "subject_1"
},
"action": {
"addLabelIds": [
"Label_id_1"
],
"removeLabelIds": [
"INBOX",
"SPAM"
]
}
},
{
"id": "id_2",
"criteria": {
"from": "mail#domain2.com",
"subject": "subject_1"
},
"action": {
"addLabelIds": [
"Label_id_2"
],
"removeLabelIds": [
"INBOX",
"SPAM"
]
}
}
]
}
And I would like to extract emails values : mail#domain1.com and mail#domain2.com
I have tried this command:
jq --raw-output '.filter[] | select(.criteria.from | test("mail"; "i")) | .id'
But does not work, I get this error :
jq: error (at <stdin>:1206): null (null) cannot be matched, as it is
not a string exit status 5
Another point : how to display the value of "id" key, where "from" key value = mail#domain1.com ?
So in my file id = id_1
Do you have an idea ?
If you only need to extract the emails from .criteria.from then this filter is enough as far as I can tell:
jq --raw-output '.filter[].criteria.from' file.json
If some objects don't have a criteria object then you can filter out nulls with:
jq --raw-output '.filter[].criteria.from | select(. != null)' file.json
If you want to keep the emails equal to "mail#domain1.com":
jq --raw-output '.filter[].criteria.from | select(. == "mail#domain1.com")' file.json
If you want to keep the emails that start with "mail#":
jq --raw-output '.filter[].criteria.from | select(. != null) | select(startswith("mail#"))' file.json
I would like to extract emails values
There is a wide spectrum of possible answers, with these
amongst the least specific with respect to where in the JSON the email addresses occur:
.. | objects | .from | select(type=="string")
.. | strings | select(test("#([a-z0-9]+[.])+[a-z]+$"))

Jq getting output from nested hash

Hi I am trying to parse below hash with jq
{
"name": "a",
"data": [
{
"sensitive": false,
"type": "string",
"value": "mykeypair"
},
{
"sensitive": false,
"type": "int",
"value": 123
}
]
}
and get output like
a,string,mykeypair
a,int,123
I am able to get output like this
a,string,mykeypair
a,int,mykeypair
a,string,123
a,int,123
jq solution:
jq -r '.name as $n | .data[] | [$n, .type, .value] | #csv' file.json
The output:
"a","string","mykeypair"
"a","int",123
If it's mandatory to output unquoted values:
jq -r '.name as $n | .data[] | [$n, .type, "\(.value)"] | join(",")' file.json
The output:
a,string,mykeypair
a,int,123

How do i add an index in jq

I want to use jq map my input
["a", "b"]
to output
[{name: "a", index: 0}, {name: "b", index: 1}]
I got as far as
0 as $i | def incr: $i = $i + 1; [.[] | {name:., index:incr}]'
which outputs:
[
{
"name": "a",
"index": 1
},
{
"name": "b",
"index": 1
}
]
But I'm missing something.
Any ideas?
It's easier than you think.
to_entries | map({name:.value, index:.key})
to_entries takes an object and returns an array of key/value pairs. In the case of arrays, it effectively makes index/value pairs. You could map those pairs to the items you wanted.
A more "hands-on" approach is to use reduce:
["a", "b"] | . as $in | reduce range(0;length) as $i ([]; . + [{"name": $in[$i], "index": $i}])
Here are a few more ways. Assuming input.json contains your data
["a", "b"]
and you invoke jq as
jq -M -c -f filter.jq input.json
then any of the following filter.jq filters will generate
{"name":"a","index":0}
{"name":"b","index":1}
1) using keys and foreach
foreach keys[] as $k (.;.;[$k,.[$k]])
| {name:.[1], index:.[0]}
EDIT: I now realize a filter of the form foreach E as $X (.; .; R) can almost always be rewritten as E as $X | R so the above is really just
keys[] as $k
| [$k, .[$k]]
| {name:.[1], index:.[0]}
which can be simplified to
keys[] as $k
| {name:.[$k], index:$k}
2) using keys and transpose
[keys, .]
| transpose[]
| {name:.[1], index:.[0]}
3) using a function
def enumerate:
def _enum(i):
if length<1
then empty
else [i, .[0]], (.[1:] | _enum(i+1))
end
;
_enum(0)
;
enumerate
| {name:.[1], index:.[0]}

Resources