Доступ к свойству PSObject косвенно с помощью переменной

Скажем, у меня есть JSON, как:

  {
    "a" : {
        "b" : 1,
        "c" : 2,
        }
  }

Сейчас ConvertTo-Json будет счастливо создавать PSObjects из этого. Я хочу получить доступ к предмету, который мог бы сделать $json.a.b и получить 1 - красиво вложенные свойства.

Теперь, если у меня есть строка "a.b" вопрос в том, как использовать эту строку для доступа к тому же элементу в этой структуре? Похоже, должен быть какой-то особый синтаксис, который мне не хватает & для динамических вызовов функций, потому что в противном случае вы должны интерпретировать строку самостоятельно, используя Get-Member неоднократно ожидаю.

3 ответа

Решение

Нет, специального синтаксиса нет.

Самое простое решение - использовать Invoke-Expression, что хорошо в этом ограниченном сценарии, но Invoke-Expression как правило, следует избегать:

$json = @'
{
  "a" : {
      "b" : 1,
      "c" : 2,
      }
}
'@

$obj = ConvertFrom-Json $json

# The path to the target property.
$propertyPath = 'a.b'

# Construct the expression and pass it to Invoke-Expression.
# Note the need to `-escape the `$` in `$obj` to prevent premature expansion.
Invoke-Expression "`$obj.$propertyPath"

Выше эквивалентно выполнению $obj.a.b. напрямую и дает 1,


В качестве альтернативы, вы можете написать простую вспомогательную функцию:

function propByPath($obj, $propertyPath) {
  foreach ($prop in $propertyPath -split '\.')  { $obj = $obj.$prop }
  $obj # output
}

Вместо Invoke-Expression Позвоните, вы бы использовали:

propByPath $obj $propertyPath

Вы даже можете использовать ETS PowerShell (система расширенного типа) для подключения .GetPropByPath() метод для всех [pscustomobject] экземпляры (PSv3 + синтаксис; в PSv2 вам придется создать *.types.ps1xml файл и загрузить его Update-TypeData -PrependPath):

'System.Management.Automation.PSCustomObject',
'Deserialized.System.Management.Automation.PSCustomObject' |
  Update-TypeData -TypeName { $_ } `
                  -MemberType ScriptMethod -MemberName GetPropByPath -Value {                  #`
    param($propPath)
    $obj = $this
    foreach ($prop in $propPath -split '\.')  { $obj = $obj.$prop }
    $obj # output
  }

Вы могли бы тогда позвонить $obj.GetPropByPath('a.b'),

Примечание: тип Deserialized.System.Management.Automation.PSCustomObject является целью в дополнение к System.Management.Automation.PSCustomObject чтобы также охватить десериализованные пользовательские объекты, которые возвращаются в ряде сценариев, таких как использование Import-CliXml получение выходных данных от фоновых заданий и использование удаленного взаимодействия.

.GetPropByPath() будет доступен на любом [pscustomobject] экземпляр в оставшейся части сеанса (даже в экземплярах, созданных до Update-TypeData звоните [1]); поместите Update-TypeData позвоните в ваш $PROFILE (файл профиля), чтобы сделать метод доступным по умолчанию.


Более надежное решение, которое поддерживает индексирование и сохраняет свойства массива как таковые

Вышеуказанное решение:

  • не поддерживает индексы как часть пути свойства (например, 'a.b[2]')
  • разворачивает свойства со значениями массива с помощью конвейерной логики, что означает, что одноэлементный массив разворачивается в своем единственном элементе.

Следующее решение устраняет эти ограничения, но учтите, что:

  • Поддерживаются только буквенные скалярные индексы (то есть вы можете использовать 'a.b[2]', но нет 'a.b[1..2]' или же 'a.b[1, 2]', например)

  • Для свойств, которые являются хеш-таблицами, укажите (буквальное) имя ключа без встроенных кавычек (например, 'a.ht[bar]'); обратите внимание, что вы не сможете получить доступ к числовым ключам хеш-таблицы в целом, и, кроме того, вы не сможете получить доступ к записям упорядоченного хеш-таблицы по индексу.

'System.Management.Automation.PSCustomObject',
'Deserialized.System.Management.Automation.PSCustomObject' |
  Update-TypeData -TypeName { $_ } `
                  -MemberType ScriptMethod -MemberName GetPropByPath -Value {                  #`
    param($propPath)
    $obj = $this
    foreach ($prop in $propPath -split '\.')  {
      # See if the property spec has an index (e.g., 'foo[3]')
      if ($prop -match '(.+?)\[(.+?)\]$') {
        $obj = $obj.($Matches.1)[$Matches.2]
      } else {
        $obj = $obj.$prop
      }
    }
    # Output: If the value is a collection (array), output it as a
    #         *single* object.
    if ($obj.Count) {
      , $obj
    } else {
      $obj
    }
  }

[1] Подтвердить с помощью (все в одной строке) $co = New-Object PSCustomObject; Update-TypeData -TypeName System.Management.Automation.PSCustomObject -MemberType ScriptMethod -MemberName GetFoo -Value { 'foo' }; $co.GetFoo() какие выводы foo даже если $co был создан раньше Update-TypeData назывался.

Этот обходной путь может быть кому-то полезен.

Результат всегда идет глубже, пока не попадает в нужный объект.

      $json=(Get-Content ./json.json | ConvertFrom-Json)

$result=$json
$search="a.c"
$search.split(".")|% {$result=$result.($_) }
$result


У вас может быть 2 переменных. Мне пришлось вынуть последнюю запятую из json, чтобы избежать ошибки «ConvertFrom-Json: Invalid JSON primitive:».

      $json = '{
    "a" : {
        "b" : 1,
        "c" : 2
        }
  }' | convertfrom-json
$a,$b = 'a','b'
$json.$a.$b
      1
Другие вопросы по тегам