Как разобрать json с jq, где структура нелинейная - PullRequest
0 голосов
/ 20 января 2020

Надеюсь, я четко изложил свою проблему. Нужна помощь в запросах и разборе нескольких файлов json с использованием JQ, где структура нелинейна в каждом файле. Приложение выдает данные конфигурации, которые могут выглядеть следующим образом. В одном файле может быть ноль или много объектов DualEndPoint или Local. Мне нужно иметь возможность запросить указанного пользователя c в атрибуте «Пользователь» и вставить новый пароль для повторной отправки обратно в API. Для DualEndPoints имена вложенных объектов являются переменными, поэтому невозможно найти эти значения при поиске атрибута «Пользователь».

Если найдено совпадение для определенного пользователя c, вернуть всю структуру только с этим введен новый пароль пользователя. В этом примере запрос для user1 вернул бы весь PROFILE1 и PROFILE2, но не PROFILE3, поскольку он не содержит учетные данные user1.

{
  "PROFILE1": {
    "Type": "ConnectionProfile:FileTransfer:DualEndPoint",
    "WorkloadAutomationUsers": [
      "*"
    ],
    "VerifyBytes": true,
    "TargetAgent": "sqlrptvmjhbpr01",
    "TargetCTM": "Production",
    "Endpoint:Src:Local_0": {
      "Type": "Endpoint:Src:Local",
      "User": "user1",
      "Port": "0",
      "OsType": "Windows",
      "HostName": "Local",
      "Password": "*****",
      "HomeDirectory": "/user1homedir"
    },
    "Endpoint:Dest:SFTP_1": {
      "Type": "Endpoint:Dest:SFTP",
      "User": "user2",
      "HostName": "server2",
      "Password": "*****",
      "HomeDirectory": "/user2homedir"
    }
  },
  "PROFILE2": {
    "Type": "ConnectionProfile:FileTransfer:Local",
    "WorkloadAutomationUsers": [
      "*"
    ],
    "VerifyBytes": true,
    "User": "user1",
    "VerifyDestination": true,
    "OsType": "Windows",
    "HostName": "Local",
    "Password": "*****",
    "TargetAgent": "server1",
    "TargetCTM": "Production"
  },
  "PROFILE3": {
    "Type": "ConnectionProfile:FileTransfer:Local",
    "WorkloadAutomationUsers": [
      "*"
    ],
    "VerifyBytes": true,
    "User": "user3",
    "OsType": "Windows",
    "HostName": "Local",
    "Password": "*****",
    "HomeDirectory": "/user3hoemdir",
    "TargetAgent": "server2",
    "TargetCTM": "Production"
  }
}

Ответы [ 3 ]

2 голосов
/ 20 января 2020

С jq 1.6 вы можете использовать следующее:

jq --arg newPwd "newPassword" \
     'walk(if type == "object" and .User == "user1" then .password |= $newPwd else . end)  
        | map_values(select(.. | select(type == "object") and .User == "user1"))' 

Это будет повторяться по вашему JSON вводу и устанавливать поле password объектов, которые имеют ключ / значение User : "user1" сопряжение с желаемым значением.

Вы можете попробовать здесь .

В предыдущих версиях вы можете использовать этот эквивалент:

jq --arg newPwd "newPassword" \
   'def rec :
      if type == "object" and .User == "user1" then 
        .password = $newPwd
      elif type == "object" then
        map_values(rec)
      elif type == "array" then
        map(rec)
      else
        .
      end
    ; 
    rec  | map_values(select(.. | select(type == "object") and .User == "user1"))'

Вы можете попробуйте здесь .

1 голос
/ 21 января 2020

В следующем решении указанной проблемы есть два шага. На первом шаге используется with_entries для выбора соответствующих объектов «ПРОФИЛЬ», а на втором шаге используется walk для обновления пароля при наличии пароля. Все достаточно просто параметризовать, поэтому для простоты предположим (как в Q), что пользователь "user1":

with_entries(select( .value
    | any(paths(. == "user1");
          .[-1] == "User" )))
| walk( if type == "object" and .User == "user1" and has("Password")
        then .Password = "newpassword"
        else .end)

Использование any здесь немного усложняет, но для эффективность.

Примечание на walk/1

Если ваш jq не имеет walk/1, то сейчас самое время обновить ваш jq, но если это не вариант, просто Google для его def (условия поиска: jq def walk builtin.jq) и скопируйте def в начало вашей jq-программы.

0 голосов
/ 20 января 2020

Добро пожаловать в StackOverflow!

Полный пример не был допустимым JSON объектом, и не предпринимались попытки решить проблему или пример того, как должен выглядеть желаемый результат. Таким образом, следующий ответ приходит с небольшим количеством догадок. Вероятно, это значительно увеличило бы шансы на получение хорошего ответа, если бы пример был урезан до минимального воспроизводимого примера , в котором удален весь беспорядок.

Например, "TargetAgent": "sqlrptvmjhbpr01" представляется неактуальной информацией. Единственный эффект, который дает эта строка, состоит в том, чтобы увеличить когнитивную нагрузку на читателя при попытке расшифровки, если она имеет отношение к проблемной задаче, которой, по-видимому, нет.

Может быть ноль или много объекты DualEndPoint или Local на файл.

Вы не можете точно сказать, что такое объект DualEndPoint или Local.

Поскольку текст DualEndPoint встречается только в контекст

"Type": "ConnectionProfile:FileTransfer:DualEndPoint"

Я предполагаю, что объект DualEndPoint - это объект, который содержит пару ключ-значение в формате

"Type": "...:DualEndPoint"

, а объект Local является то же самое, но с DualEndPoint, замененным на Local. Если эта интерпретация верна, то в вашем первом фрагменте кода будет три примера Local объектов на двух разных уровнях вложенности (что я понимаю как «нелинейная» часть).

Один из примеров объекта Local будет следующим:

{
  "Type": "Endpoint:Src:Local",
  "User": "user1",
  "Port": "0",
  "OsType": "Windows",
  "HostName": "Local",
  "Password": "newpassword",
  "HomeDirectory": "/user1homedir"
}

Нет примеров подобных объектов, которые, несмотря на наличие атрибута «Пользователь», не должны обновляться. Таким образом, похоже, что для ответа на вопрос, различие между этими типами объектов также совершенно не нужно?

Мне нужно иметь возможность запросить определенного пользователя c в атрибуте «Пользователь» и вставьте новый пароль для повторной отправки обратно в API.

Таким образом, подзадача вашей главной проблемы может состоять в том, чтобы обновить объект с новым паролем, если он правильный User. Предполагая, что вы ограничили объект до такого объекта, часть программы может выглядеть следующим образом:

$ jq 'if .User == "user1" then .Password = "derp" else . end' local1.json
{
  "Type": "Endpoint:Src:Local",
  "User": "user1",
  "Port": "0",
  "OsType": "Windows",
  "HostName": "Local",
  "Password": "derp",
  "HomeDirectory": "/user1homedir"
}

Для DualEndPoints имена вложенных объектов являются переменными, поэтому нельзя кодировать их значения в поиске атрибута «Пользователь».

Похоже, вы хотите произвольно повторить поиск объектов с атрибутами «Пользователь». Некоторые из рекурсивных комбинаторов jq: .., более общий recurse и, что представляется более уместным в этом контексте, walk:

$ jq 'walk(if type == "object" and .User == "user1"
           then .Password = "derp"
           else . end)' full.json

(Это также что написал Аарон, за исключением того, что он использует |=, а я использую =.)

См. его пример jqplay или этот пример jqplay .

Где совпадение для указанного пользователя c найдено, возвращает всю структуру, в которой вставлен только новый пароль этого пользователя. В этом примере запрос для user1 вернул бы весь PROFILE1 и PROFILE2, но не PROFILE3, поскольку он не содержит учетные данные user1.

Это звучит как дополнительное условие в выражении, с которым мы ходим:

$ jq 'walk(if type == "object" and has("User")
           then (if .User == "user1"
                 then .Password = "derp"
                 else null end)
           else . end)' full.json

Похоже, что это почти работает (см. в этом примере jqplay ), за исключением того, что он оставляет значения "foo": null в результате ходьбы. Это побочный продукт того, что мы уже вернулись в объект, содержащий свойство «Пользователь», что затрудняет express удаление родительской пары ключ-значение.

Исправление этого нам нужно либо посмотреть в фильтр walk/1, либо создать заполнитель и пройти второй раз с точки зрения родительского объекта. Последняя из этих двух стратегий демонстрируется здесь:

$ jq 'walk(if type == "object" and has("User")
      then (if .User == "user1" then .Password = "derp" else "wat" end)
      else . end)
      | walk(if type == "object"
             then with_entries(select(.value != "wat"))
             else . end)' full.json

Это работает. Смотрите этот пример jqplay .

...