Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generate new array from sparse array of objects in JQ

Tags:

jq

I have a JSON file that I want to process with JQ. It has an array of objects inside another object, with a key that I want to use to populate a new array.

In my real use-case this is nested in with a lot of other fluff and there lots more arrays but take this as a simpler but representative example of the kind of thing:

{
  "numbers": [
    {
      "numeral": 1,
      "ordinal": "1st",
      "word": "One"
    },
    {
      "numeral": 2,
      "ordinal": "2nd",
      "word": "Two"
    },
    {
      "numeral": 5,
      "ordinal": "5th",
      "word": "Five"
    },
    {
      "some-other-fluff-i-want-to-ignore": true
    }
  ]
}

I'd like to use JQ to get a new array based on the elements, ignoring some elements and handling the missing ones. e.g.

[
  "The 1st word is One",
  "The 2nd word is Two",
  "Wot no number 3?",
  "Wot no number 4?",
  "The 5th word is Five"
]

Doing this in a loop for the elements that are there is simple, terse and elegant enough:

.numbers | map( . | select( .numeral) | [ "The", .ordinal, "word is", .word ] | join (" "))

But I can't find a way to cope with the missing entries. I have some code that sort-of works:

.numbers | [
  ( .[] | select(.numeral == 1) | ( [ "The", .ordinal, "word is", .word ] | join (" ")) ) // "Wot no number 1?",
  ( .[] | select(.numeral == 2) | ( [ "The", .ordinal, "word is", .word ] | join (" ")) ) // "Wot no number 2?",
  ( .[] | select(.numeral == 3) | ( [ "The", .ordinal, "word is", .word ] | join (" ")) ) // "Wot no number 3?",
  ( .[] | select(.numeral == 4) | ( [ "The", .ordinal, "word is", .word ] | join (" ")) ) // "Wot no number 4?",
  ( .[] | select(.numeral == 5) | ( [ "The", .ordinal, "word is", .word ] | join (" ")) ) // "Wot no number 5?"
]

It produces usable output, after a fashion:

richard@sophia:~$ jq -f make-array.jq < numbers.json
[
  "The 1st word is One",
  "The 2nd word is Two",
  "Wot no number 3?",
  "Wot no number 4?",
  "The 5th word is Five"
]
richard@sophia:~$ 

However, whilst it produces the output, handles the missing elements and ignores the bits I don't want, it's obviously extremely naff code that cries out for a for-loop or something similar but I can't see a way in JQ to do this. Any ideas?

like image 868
Richard Wheeldon Avatar asked Jan 19 '26 22:01

Richard Wheeldon


1 Answers

jq solution:

jq 'def print(o): "The \(o.ordinal) word is \(o.word)";
    .numbers | (reduce map(select(.numeral))[] as $o ({}; .["\($o.numeral)"] = $o)) as $o
    | [range(0; ($o | [keys[] | tonumber] | max)) 
       | "\(.+1)" as $i
       | if ($o[$i]) then print($o[$i]) else "Wot no number \($i)?" end
    ]' input.json

The output:

[
  "The 1st word is One",
  "The 2nd word is Two",
  "Wot no number 3?",
  "Wot no number 4?",
  "The 5th word is Five"
]
like image 184
RomanPerekhrest Avatar answered Jan 22 '26 14:01

RomanPerekhrest



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!