Powershell: Как динамически обновить вложенный массив JSON? - PullRequest
2 голосов
/ 27 марта 2019

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

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

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

Эта ссылка: Powershell: как обновить / заменить данные и значения в Json и объекте XML показывает (своего рода) "реальную" структуру JSON, но принятый ответ опирается на знание структуры JSON есть (ОП не обращался за помощью по динамическому пути).

Эта ссылка: Задать значение свойства вложенного объекта по имени в PowerShell имеет что-то очень близкое, хотя, когда массив находится в миксе, он не работает должным образом при настройке.

Ниже приведен пример использования JSON для решения этой проблемы, хотя, опять же, структура не известна до запуска сценария. Я перебираю список файлов на диске и выполняю для каждого файла.

$JSON = ConvertFrom-Json '{
   "key1":"key 1 value",
   "options":{
      "outDir":"./app-dir",
      "lib":[
         "someLibrary",
         "anotherLibrary"
      ],
      "someObjects":[
         {
            "first":"I am first"
         },
         {
            "second":"I am second"
         }
      ]
   }
}'

Строка для поиска этого json может выглядеть следующим образом:

$SearchString = 'options.someObjects.first'

Или, может быть, что-то несуществующее, как:

$SearchString = 'options.someObjects.foo'

Использование рекурсивной функции GetValue из 2-й статьи прекрасно работает для получения (и гораздо более элегантно, чем то, что я делал):

function GetValue($object, $key)
{
    $p1,$p2 = $key.Split(".")
    if($p2) { return GetValue -object $object.$p1 -key $p2 }
    else { return $object.$p1 }
}

Однако функция SetValue не работает с массивом. Он возвращает ошибку о том, что «свойство first не может быть найдено для этого объекта».

function SetValue($object, $key, $Value)
{
    $p1,$p2 = $key.Split(".")
    if($p2) { SetValue -object $object.$p1 -key $p2 -Value $Value }
    else { $object.$p1 = $Value }
}

Я знаю, что это потому, что $ JSON.options.someObjects является массивом, поэтому для доступа к объекту с помощью «первого» ключа путь будет:

$JSON.options.someObjects[0].first

Это проблема, с которой я столкнулся. Как мне динамически перебирать все объекты, как только они достигают части пути, которая нуждается в переборах? Эта часть пути может быть где угодно, или больше уровней внизу и т. Д.

Странно, что powershell позволит вам динамически перебирать массив при получении значения, но не при попытке его установить.

Вот полный пример, который демонстрирует всю проблему:

#Create JSON:
$JSON = ConvertFrom-Json '{
    "key1":"key 1 value",
    "options":{
       "outDir":"./app-dir",
       "lib":[
          "someLibrary",
          "anotherLibrary"
       ],
       "someObjects":[
          {
             "first":"I am first"
          },
          {
             "second":"I am second"
          }
       ]
    }
}'


$SearchPath = 'options.someObjects.first'
$NewValue = 'I am a new value'

function GetValue($object, $key)
{
    $p1,$p2 = $key.Split(".")
    if($p2) { GetValue -object $object.$p1 -key $p2 }
    else { return $object.$p1 }
}

function SetValue($object, $key, $Value)
{
    $p1,$p2 = $key.Split(".")
    if($p2) { SetValue -object $object.$p1 -key $p2 -Value $Value }
    else { return $object.$p1 = $Value }
}


GetValue -object $JSON -key $SearchPath

SetValue -object $JSON -key $SearchPath -Value $NewValue

Я искал всевозможные термины, пытаясь найти хорошее решение для этой проблемы, но пока безуспешно. Я вполне уверен, что я не первый человек, который хочет делать подобные вещи, извинения, если я где-то пропустил ответ.

1 Ответ

2 голосов
/ 27 марта 2019

С вашим скриптом SetValue есть две проблемы:

  • Возвращение объекта
  • Объект ([Object]) против массива объектов ([Object[]])

Return

Невозможно вернуть назначение, подобное return $object.$p1 = $Value.Само присваивание ничего не возвращает с, что приведет к возвращению $Null вызывающему.
Кроме того, если вы возвращаете $Object для каждого рекурсивного вызова, вам нужно будет аннулировать ($Null = SetValue -object...) его каждым родительским вызывающим, такчто он возвращается только вызывающим абонентом.но имейте в виду, что вы на самом деле тыкаете $NewValue в исходный ($JSON) объект !.Если вы этого не хотите, вам нужно выяснить, кто звонит, и только скопировать $Object на верхнем уровне перед рекурсивным вызовом.

Массив объектов

Вы имеете дело не только со свойствами, содержащими отдельные объекты, но каждое свойство может потенциально содержать объекты collection .Фактически, свойство листа SomeObject является примером этого.Это означает, что у каждого объекта в коллекции есть свой собственный уникальный набор свойств (который может иметь то же имя свойства, что и у родственного объекта):

$JSON = ConvertFrom-Json '{
   "key1":"key 1 value",
   "options":{
      "outDir":"./app-dir",
      "lib":[
         "someLibrary",
         "anotherLibrary"
      ],
      "someObjects":[
         {
            "first":"I am first"
         },
         {
            "first":"I am first too"
         },
         {
            "second":"I am second"
         }
      ]
   }
}'

Обратите внимание, что вы можете столкнуться сколлекция объектов на каждом уровне объекта Json.
Начиная с PSv3, у вас есть функция с именем Перечисление членов , которая позволяет перечислять эти свойства в виде, например: ([Object[]]$SomeObject).First, но вы не можете просто установить (все) соответствующие свойства, как это: ([Object[]]$SomeObject).First = $Value.(Вот почему ваша SetValue функция не работает, а ваша GetValue функция работает. Обратите внимание, что она на самом деле возвращает два элемента для приведенного выше примера Json «I am first too»).

Ответ

Другими словами, вам нужно будет перебрать все коллекции объектов на каждом уровне, чтобы установить соответствующее свойство:

function SetValue($object, $key, $Value)
{
    $p1,$p2 = $key.Split(".",2)
    if($p2) { $object | ?{$Null -ne $_.$p1} | %{SetValue -object $_.$p1 -key $p2 -Value $Value} }
    else { $object | ?{$Null -ne $_.$p1} | %{$_.$p1 = $Value} }
}

SetValue -object $JSON -key $SearchPath -Value $NewValue

$Json | ConvertTo-Json -Depth 5
{
    "key1":  "key 1 value",
    "options":  {
                    "outDir":  "./app-dir",
                    "lib":  [
                                "someLibrary",
                                "anotherLibrary"
                            ],
                    "someObjects":  [
                                        {
                                            "first":  "I am a new value"
                                        },
                                        {
                                            "second":  "I am second"
                                        }
                                    ]
                }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...