Доступ к свойству 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