Search code examples
jsonjq

Flatten and filter multi-dimension JSON with JQ


I have the following JSON test.json:

[
  {
    "status": {
      "+@state": "up"
    },
    "address": {
      "+@addr": "10.10.10.1",
      "+@addrtype": "ipv4"
    },
    "ports": {
      "port": [
        {
          "+@protocol": "tcp",
          "+@portid": "80",
          "state": {
            "+@state": "open"
          }
        },
        {
          "+@protocol": "tcp",
          "+@portid": "443",
          "state": {
            "+@state": "closed"
          }
        }
      ]
    }
  },
  {
    "status": {
      "+@state": "down"
    },
    "address": {
      "+@addr": "10.10.10.2",
      "+@addrtype": "ipv4"
    },
    "ports": {
      "port": [
        {
          "+@protocol": "tcp",
          "+@portid": "21",
          "state": {
            "+@state": "closed"
          }
        },
        {
          "+@protocol": "tcp",
          "+@portid": "22",
          "state": {
            "+@state": "closed"
          }
        }
      ]
    }
  },
  {
    "status": {
      "+@state": "up"
    },
    "address": {
      "+@addr": "10.10.10.3",
      "+@addrtype": "ipv4"
    },
    "ports": {
      "port": [
        {
          "+@protocol": "tcp",
          "+@portid": "21",
          "state": {
            "+@state": "closed"
          }
        },
        {
          "+@protocol": "tcp",
          "+@portid": "22",
          "state": {
            "+@state": "closed"
          }
        }
      ]
    }
  },
  {
    "status": {
      "+@state": "up"
    },
    "address": {
      "+@addr": "10.10.10.4",
      "+@addrtype": "ipv4"
    },
    "ports": {
      "port": [
        {
          "+@protocol": "tcp",
          "+@portid": "21",
          "state": {
            "+@state": "open"
          }
        },
        {
          "+@protocol": "tcp",
          "+@portid": "22",
          "state": {
            "+@state": "open"
          }
        }
      ]
    }
  }
]

Running jq --null-input '[ inputs | .[] | select( .status."+@state" == "up" and .ports.port[].state."+@state" == "open" ) | { address: .address."+@addr", port: .ports.port } ]' test.json gets me the following:

[
  {
    "address": "10.10.10.1",
    "port": [
      {
        "+@protocol": "tcp",
        "+@portid": "80",
        "state": {
          "+@state": "open"
        }
      },
      {
        "+@protocol": "tcp",
        "+@portid": "443",
        "state": {
          "+@state": "closed"
        }
      }
    ]
  },
  {
    "address": "10.10.10.4",
    "port": [
      {
        "+@protocol": "tcp",
        "+@portid": "21",
        "state": {
          "+@state": "open"
        }
      },
      {
        "+@protocol": "tcp",
        "+@portid": "22",
        "state": {
          "+@state": "open"
        }
      }
    ]
  },
  {
    "address": "10.10.10.4",
    "port": [
      {
        "+@protocol": "tcp",
        "+@portid": "21",
        "state": {
          "+@state": "open"
        }
      },
      {
        "+@protocol": "tcp",
        "+@portid": "22",
        "state": {
          "+@state": "open"
        }
      }
    ]
  }
]

How do I flatten the port to one object per port, with the address and filter where the port state == "open"? For example, I would like the output to be:

[
  {
    "address": "10.10.10.1",
    "port": "80"
  },
  {
    "address": "10.10.10.4",
    "port": "21"
  },
  {
    "address": "10.10.10.4",
    "port": "22"
  }
]

Solution

  • You're looking for something like this:

    map(select(.status."+@state" == "up") | {
      address: .address."+@addr",
      port: .ports.port[] | select(.state."+@state" == "open") ."+@portid"
    })
    

    This doesn't need --null-input.