jq — выбирать и фильтровать только определенные объекты из массива

#json #select #jq

#json #выберите #jq

Вопрос:

Мой файл JSON имеет следующую структуру

 [
    {
        "Header": {
            "Tenant": "T1"
        },
        "Body": [
            {
                "agentTechnologyType": "JAVA",
                "entityId": "SERVICE-EDA448F07EDBDAA6",
                "serviceType": "WebRequest",
                "webServerName": "www.example.com:443"
            },
            {
                "agentTechnologyType": "TOMCAT",
                "entityId": "SERVICE-728B1FF49D132C89",
                "serviceType": "WebRequest",
                "webServerName": "www.example.com:80"
            },
            {
                "agentTechnologyType": "JAVA",
                "entityId": "SERVICE-42A0FB8666B36EB4",
                "serviceType": "Process"
            },
            {
                "agentTechnologyType": "APACHE",
                "serviceType": "WebRequest",
                "webServerName": "www.example.com:443"
            }
        ]
    }
]
  

Мне нужно применить несколько фильтров и оставить в .Body[] массиве объекты в соответствии с разными критериями:

  1. Выберите только те объекты в .Body[] массиве, у которых нет .webServerName ключа — это условие должно оставить только объект # 2 внутри .Body[] массива
  2. Выберите только эти объекты в .Body[] массиве, где .webServerName значение равно www.example.com:443 — это должно оставить объекты # 0,3.
  3. Выберите все объекты, у которых есть .webServerName ключ, поэтому отрицание условия # 1 — это должно оставить все объекты, кроме # 2.

Для # 1 приведенный ниже фильтр выполняет эту работу

 [.[].Header as $x |  (.[].Body[] | select( (.webServerName == null  ))) as $y | {Tenant: $x, Body: [$y]} ]
  

Это дает следующий результат:

 [
  {
    "Tenant": {
      "Tenant": "T1"
    },
    "Body": [
      {
        "agentTechnologyType": "JAVA",
        "entityId": "SERVICE-42A0FB8666B36EB4",
        "serviceType": "Process"
      }
    ]
  }
]
  

Но когда я попытался изменить фильтр для условия # 2

 [.[].Header as $x |  (.[].Body[] | select( (.webServerName == "www.example.com:443"  ))) as $y | {Tenant: $x, Body: [$y]} ] 
  

Это дало мне следующий результат — две записи вместо одной.

 [
  {
    "Tenant": {
      "Tenant": "T1"
    },
    "Body": [
      {
        "agentTechnologyType": "JAVA",
        "entityId": "SERVICE-EDA448F07EDBDAA6",
        "serviceType": "WebRequest",
        "webServerName": "www.example.com:443"
      }
    ]
  },
  {
    "Tenant": {
      "Tenant": "T1"
    },
    "Body": [
      {
        "agentTechnologyType": "APACHE",
        "serviceType": "WebRequest",
        "webServerName": "www.example.com:443"
      }
    ]
  }
]
  

В то время как ожидаемый результат должен выглядеть как для случая # 2:

 [
  {
    "Tenant": {
      "Tenant": "T1"
    },
    "Body": [
      {
        "agentTechnologyType": "JAVA",
        "entityId": "SERVICE-EDA448F07EDBDAA6",
        "serviceType": "WebRequest",
        "webServerName": "www.example.com:443"
      },
      {
        "agentTechnologyType": "APACHE",
        "serviceType": "WebRequest",
        "webServerName": "www.example.com:443"
      }
    ]
  }
]
  

И следующее для случая # 3:

 [
  {
    "Header": {
      "Tenant": "T1"
    },
    "Body": [
      {
        "agentTechnologyType": "JAVA",
        "entityId": "SERVICE-EDA448F07EDBDAA6",
        "serviceType": "WebRequest",
        "webServerName": "www.example.com:443"
      },
      {
        "agentTechnologyType": "TOMCAT",
        "entityId": "SERVICE-728B1FF49D132C89",
        "serviceType": "WebRequest",
        "webServerName": "www.example.com:80"
      },
      {
        "agentTechnologyType": "APACHE",
        "serviceType": "WebRequest",
        "webServerName": "www.example.com:443"
      }
    ]
  }
]
  

Я пытаюсь выяснить, где я допустил ошибку — в условии выбора или при формировании нового объекта в конце.

Ответ №1:

Судя по вашим требованиям, кажется, вам нужно выражение для выбора объектов в .Body[] массиве, где .webServerName есть ключ www.example.com:443 или ключ в принципе существует

 .[].Body |= map(select(.webServerName == "www.example.com:443" or has("webServerName") ))
  

Обе ваши попытки, хотя и действительны, действительно запутаны. Стандартный шаблон для выборочной фильтрации объектов внутри массива — использовать |= оператор с правильным select выражением.

| {Tenant: $x, Body: [$y]} Неверно, потому что оно применяется к каждому из 2 объектов, которые вы отфильтровали в предыдущем выражении. Таким образом, для каждого результата создано Tenant поле. Поскольку у вас нет изменений в именах ключей (тот же ключ Tenant ), на самом деле нет смысла заново создавать новые объекты {..} .

Комментарии:

1. Спасибо. Мой вопрос — почему .webServerName != "www.example.com:443" выдает вывод, который включает объект, у которого нет ключа webServerName внутри? Я понимаю, что могу включить дополнительный фильтр null , но хочу понять механизм.