skip to Main Content

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?

3

Answers


  1. You always have the option of parsing and implementing the comparisons. Make it as robust as needed.

    def parse_semver($with_op): 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);
    def parse_semver: parse_semver(false);
    def cmp_semver($other): parse_semver as $a | ($other|parse_semver(true)) as $b
        # modified and simplified 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
            if $a.prerelease == null then 1
            elif $b.prerelease == null then -1
            elif $a.prerelease > $b.prerelease then 1 else -1 end
        elif $a.build != $b.build then
            if $a.build > $b.build then 1 else -1 end
        else
            0
        end;
    

    Then utilize the new comparison function:

    $ jq 'map(select(.version | cmp_semver("1.2.0") == 0))' input.json
    
    Login or Signup to reply.
  2. If you need to get all the objects where the version is "1.2.0", you can use string comparison :

    jq --arg target 1.2.0 '
        map(select(.version == $target))' input.json
    

    If you want the objects with version greater than 1.2.7, then:

    jq --arg target 1.2.7 '
        def triple($i): $i | [splits("[.-]") | tonumber? // .];
        map(select(triple(.version) > triple($target)))' input.json
    
    Login or Signup to reply.
  3. I know I could do some ugly regex hack here

    You could also use a simple regex hack if the semver strings do not need to be checked for correctness. For example, consider the following:

    # Recognize 1.0.1-alpha as [1,0,1,"alpha"]
    def semver: [scan("[^-.]+") | tonumber? // .];
    

    Since a valid semver string always has three components, none of which can have superfluous leading zeros, and since jq’s built-in ordering is so friendly (as per the comment by @A.H.), semver as defined above should make it quite easy to compare valid semver strings. However, since the spec requires that "pre-release" < "release", for non-trivial semantic version strings, some care is required:

    # Compare two semver arrays ensuring in particular that:
    # pre-release < release
    # identifiers with letters or hyphens are compared lexically in ASCII sort order;
    # numeric identifiers always have lower precedence than non-numeric identifiers.
    def semver_less_than($array):
      if .[:3] == $array[:3] and length > 3 and ($array|length) == 3 then true
      else . < $array
      end;
    

    Since your example only has trivial semver specs, we could get away with:

    # Filter for finding .version greater than 1.2.7 
    ("1.2.7" | semver) as $v
    | map(select( (.version|semver) > $v))
    

    Caveat: I’m no expert on semantic versioning, and it’s quite likely the above needs some improvement. Tweak suggestions would be welcome.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search