nickpelling
nickpelling

Reputation: 131

How to merge jq JSON objects generated by comma-separated filters

I'm trying to use jq to convert some command-line flags into a JSON equivalent.

The flags look like this, where the idea is to convert the (optional) f flag into a JSON "foo" field, and the (optional) b flag into a JSON "bar" field:

{
  "flags": [
    "f1",
    "b2",
    "f3b4",
    "b6f5"
  ]
}

Getting the foo fields is easy:

.flags[] | match("f([0-9][0-9]*)") | .captures[0].string | tonumber | { "foo": . }

Same for the bar fields (please say if there are better ways to do this with jq):

.flags[] | match("b([0-9][0-9]*)") | .captures[0].string | tonumber | { "bar": . }

How can I merge the output of these two filters together so that each input flags line gets mapped to a single JSON object with none / one / both of the optional fields?

The two relevant mechanisms are jq's comma operator (to share a single stream between multiple filters) and jq's + operator (to merge together objects into a single object). Applying the comma operator is straightforward:

.flags[] | (match("f([0-9][0-9]*)") | .captures[0].string | tonumber | { "foo": . }), (match("b([0-9][0-9]*)") | .captures[0].string | tonumber | { "bar": . })

However, this yields a separate object for each match:

{
  "foo": 1
}
{
  "bar": 2
}
{
  "foo": 3
}
{
  "bar": 4
}
{
  "foo": 5
}
{
  "bar": 6
}

So the specific problem here is how to join these two objects together using the + operator. The final output I'm trying to get here is where the foo and bar fields sit together in the same object:

{
  "foo": 1
}
{
  "bar": 2
}
{
  "foo": 3,
  "bar": 4
}
{
  "foo": 5,
  "bar": 6
}

What is the best way to achieve this with jq?

Upvotes: 2

Views: 2190

Answers (1)

user197693
user197693

Reputation: 2045

The capture function seems suited to your task.

From the manual: capture(regex; flags) "Collects the named captures in a JSON object, with the name of each capture as the key, and the matched string as the corresponding value."

jq '.flags[]
| capture("(?<foo>^f\\d+$)"),
  capture("(?<bar>^b\\d+$)"),
  capture("(?<foo>f\\d+)(?<bar>b\\d+)"),
  capture("(?<bar>b\\d+)(?<foo>f\\d+)")
| .[] |= ( sub("\\D"; "") | tonumber )'

The capture lines create these objects:

{
  "foo": "f1"
}
{
  "bar": "b2"
}
{
  "foo": "f3",
  "bar": "b4"
}
{
  "bar": "b6",
  "foo": "f5"
}

The last line updates the values in those objects by removing non-digits and converting the result to a number.

Upvotes: 2

Related Questions