I have a array that looks like this:
[
{
"id": 1,
"version": "2.3.4"
},
{
"id": 2,
"version": "1.4.4"
},
{
"id": 3,
"version": "0.0.4"
},
{
"id": 4,
"version": "1.3.4"
},
]
And I need to get all the objects where the version is "1.2.0". I am interested in a built in way using JQ but I cannot find anything related. Maybe it does not exist?
I know I could do some ugly regex hack here, but what would be the right way to solve this so I can easily swap my condition so if instead of 1.2.0 maybe in a short time in the future lets say I want the objects with version greater than 1.2.7 for instance?
You always have the option of parsing and implementing the comparisons.
def _parse_semver($with_op):
if type == "string" then
capture(if $with_op then "(?<op>[~])?" else "" end
+ "(?<major>\\d+)\\.(?<minor>\\d+)(?:\\.(?<patch>\\d+))?"
+ "(?:-(?<prerelease>[A-Z0-9]+(?:\\.[A-Z0-9]+)*))?"
+ "(?:\\+(?<build>[A-Z0-9]+(?:\\.[A-Z0-9]+)*))?"; "i")
| (.major, .minor, .patch) |= (tonumber? // 0)
elif type == "object" and ([has(("major,minor,patch,prerelease,build"/",")[])]|all) then .
else empty end;
def parse_semver: _parse_semver(false);
def cmp_semver($other): parse_semver as $a | ($other|_parse_semver(true)) as $b |
def _cmp($other): if . == $other then 0 elif . > $other then 1 else -1 end;
def _cmp_dotted($other):
if . == null then 1
elif $other == null then -1
else
reduce ([split("."), ($other|split("."))] | transpose[]) as [$a, $b] (0;
if . != 0 then .
elif $a == null then -1
elif $b == null then 1
else
($a|test("^\\d+$")) as $anum | ($b|test("^\\d+$")) as $bnum |
if [$anum,$bnum] == [true,true] then $a | tonumber | _cmp($b | tonumber)
elif $anum then -1
elif $bnum then 1
else $a | _cmp($b) end
end
)
end;
# slightly modified version of https://semver.org/#spec-item-11
if $a.major != $b.major then
if $a.major > $b.major then 1 else -1 end
elif $a.minor != $b.minor then
if $a.minor > $b.minor then 1 else -1 end
elif $a.patch != $b.patch then
if $a.patch > $b.patch then 1 else -1 end
elif $b.op == "~" then
0
elif $a.prerelease != $b.prerelease then
($a.prerelease | _cmp_dotted($b.prerelease))
elif $a.build != $b.build then
($a.build | _cmp_dotted($b.build))
else
0
end;
def cmp_semver($first; $second): $first | cmp_semver($second);
Then utilize the new comparison functions:
$ jq 'map(select(.version | cmp_semver("1.2.0") > 0))' input.json
[
{
"id": 1,
"version": "2.3.4"
},
{
"id": 2,
"version": "1.4.4"
},
{
"id": 4,
"version": "1.3.4"
}
]
$ jq -rn '("1.0.0-alpha<1.0.0-alpha.1<1.0.0-alpha.beta<1.0.0-beta<1.0.0-beta.2<1.0.0-beta.11<1.0.0-rc.1<1.0.0"/"<") as $input |
range($input | length-1) |
"cmp_semver(\($input[.]|tojson);\t\($input[.+1]|tojson))\t-> "
+ "\(cmp_semver($input[.]; $input[.+1]))"'
cmp_semver("1.0.0-alpha"; "1.0.0-alpha.1") -> -1
cmp_semver("1.0.0-alpha.1"; "1.0.0-alpha.beta") -> -1
cmp_semver("1.0.0-alpha.beta"; "1.0.0-beta") -> -1
cmp_semver("1.0.0-beta"; "1.0.0-beta.2") -> -1
cmp_semver("1.0.0-beta.2"; "1.0.0-beta.11") -> -1
cmp_semver("1.0.0-beta.11"; "1.0.0-rc.1") -> -1
cmp_semver("1.0.0-rc.1"; "1.0.0") -> -1
At first I wasn't sure if it was possible to use arrays as the semver key, but it appears it is possible, but requires some additional data points to sort on.
def semver_key: parse_semver | [
.major, .minor, .patch,
.prerelease==null, ((.prerelease//"")/"."|map(tonumber? //.)),
.build==null, ((.build//"")/"."|map(tonumber? //.))
];
This allows you to sort by the versions.
$ jq -rn '
("1.0.0-alpha<1.0.0-alpha.1<1.0.0-alpha.beta<1.0.0-beta<1.0.0-beta.2<1.0.0-beta.11<1.0.0-rc.1<1.0.0"/"<") as $input |
[$input, ($input | sort_by(semver_key))] | transpose[] | "\(.[0])\t\(.[1])"
'
1.0.0-alpha 1.0.0-alpha
1.0.0-alpha.1 1.0.0-alpha.1
1.0.0-alpha.beta 1.0.0-alpha.beta
1.0.0-beta 1.0.0-beta
1.0.0-beta.2 1.0.0-beta.2
1.0.0-beta.11 1.0.0-beta.11
1.0.0-rc.1 1.0.0-rc.1
1.0.0 1.0.0
Assuming this works, this could simplify the cmp_semver/1
function greatly.
def cmp_semver2($other): semver_key as $a | ($other|semver_key) as $b |
if $a == $b then 0
elif $a > $b then 1
else -1 end;