user5971350
user5971350

Reputation:

jq - Find a JSON object based on one of its values and get another value from it

I've started using jq just very recently and I would like to know if something like this is even possible.

Example:

{
  "name": "device",
  "version": "1.0.0",
  "address": [
    {
      "address": "10.1.2.3",
      "interface": "wlan1_wifi"
    },
    {
      "address": "10.1.2.5",
      "interface": "wlan2_link"
    },
    {
      "address": "10.1.2.4",
      "interface": "ether1"
    }
  ],
  "wireless": [
    {
      "name": "wlan1_wifi",
      "type": "5Ghz",
      "ssid": "wifi"
    },
    {
      "name": "wlan2_link",
      "type": "2Ghz",
      "ssid": "link"
    }
  ]
}

Firstly let's transform the example to this json object:

cat json | jq '. | {"name": ."name", "version": ."version", "wireless": [."wireless"[] | {"name": ."name", "type": ."type", "ssid": ."ssid"}]}'
{
  "name": "device",
  "version": "1.0.0",
  "wireless": [
    {
      "name": "wlan1_wifi",
      "type": "5Ghz",
      "ssid": "wifi"
    },
    {
      "name": "wlan2_link",
      "type": "2Ghz",
      "ssid": "link"
    }
  ]
}

Now there's a problem. I need to assign an address to the "wireless" array. The address is stored in "address" array.

So the question: is there a way of finding the right json object in "address" based on "name" (in wireless array) and "interface" (in address array) for every json object in "wireless" array and then assigning "address" to it?

The final result should look like this:

{
  "name": "device",
  "version": "1.0.0",
  "wireless": [
    {
      "name": "wlan1_wifi",
      "type": "5Ghz",
      "ssid": "wifi",
      "address": "10.1.2.3"
    },
    {
      "name": "wlan2_link",
      "type": "2Ghz",
      "ssid": "link",
      "address": "10.1.2.5"
    }
  ]
}

Answer: Here's my answer based on the answer from @peak. Instead of copying the content of .wireless and then using map, I'm cherry picking the keys that I want to include only. This also allows me to position "address" how ever I want.

(INDEX(.address[]; .interface)) as $dict 
| {name: .name, version: .version, 
wireless: [.wireless[] | {name, address: ($dict[.name]|.address), type, ssid}]}

Upvotes: 5

Views: 1311

Answers (1)

peak
peak

Reputation: 116730

The following produces the output as originally requested:

(.wireless[].name) as $name
| .address[]
| select(.interface == $name)
| { wireless: {name: $name, address}}

However the above filter could potentially produce more than one result, so you might want to make modifications accordingly.

Revised revised requirements

If your jq has INDEX/2 (which was only made available AFTER jq 1.5 was released), you can simply use it to create a lookup table:

(INDEX(.address[]; .interface)) as $dict
| {name,
   version,
   wireless: (.wireless
              | map(. + {address: ($dict[.name]|.address) }) ) }

Or (depending perhaps on the exact requirements):

(INDEX(.address[]; .interface)) as $dict
| del(.address)
| .wireless |= map(. + {address: ($dict[.name]|.address) })

If your jq does not have INDEX/2, then you could easily adapt the above (using reduce), or even more easily snarf the def of INDEX/2 from https://github.com/stedolan/jq/blob/master/src/builtin.jq

Upvotes: 3

Related Questions