使用变量间接访问PSObject属性

cyb*_*org 2 powershell

说我有JSON喜欢:

  {
    "a" : {
        "b" : 1,
        "c" : 2,
        }
  }
Run Code Online (Sandbox Code Playgroud)

现在很ConvertTo-Json乐意创造PSObjects出来.我想访问我能做的项目$json.a.b并得到1 - 很好的嵌套属性.

现在,如果我有字符串"a.b",问题是如何使用该字符串访问该结构中的相同项目?似乎应该有一些特殊的语法我缺少像&动态函数调用,因为否则你必须自己Get-Member反复使用我期望的解释字符串.

mkl*_*nt0 8

不,没有特殊的语法.

最简单的解决方案是使用Invoke-Expression,这是在这种情况下受限的罚款,但Invoke-Expression通常应避免:

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

$obj = ConvertFrom-Json $json

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

# NOTE: In general, AVOID Invoke-Expression
# 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"
Run Code Online (Sandbox Code Playgroud)

以上相当于Invoke-Expression直接执行和收益$obj.a.b..


或者,您可以编写一个简单的辅助函数:

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

而不是1你将使用的电话:

propByPath $obj $propertyPath
Run Code Online (Sandbox Code Playgroud)

您甚至可以使用PowerShell的ETS(扩展类型系统)Invoke-Expression方法附加到所有.GetPropByPath()实例(PSv3 +语法;在PSv2中,您必须创建一个[pscustomobject]文件并加载它*.types.ps1xml):

'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
  }
Run Code Online (Sandbox Code Playgroud)

然后你可以打电话Update-TypeData -PrependPath.

注意:$obj.GetPropByPath('a.b')除了Deserialized.System.Management.Automation.PSCustomObject还要包括反序列化的自定义对象之外,还会定位类型,这些对象在许多方案中返回,例如使用System.Management.Automation.PSCustomObject,接收后台作业的输出以及使用远程处理.

Import-CliXml.GetPropByPath()在会话的剩余部分中的任何实例上可用(即使在[pscustomobject]调用之前创建的实例[1]); 将Update-TypeData调用放在您的Update-TypeData(配置文件)中以使该方法默认可用.


支持更健壮的解决方案的索引并保留阵列值属性为这样的

以上解决方案:

  • 不支持索引作为属性路径的一部分(例如$PROFILE)
  • 使用管道逻辑展开数组值属性,这意味着将单个元素数组解包为其唯一元素.

以下解决方案修复了这些限制,但请注意:

  • 仅支持文字标量索引(即,您可以使用'a.b[2]',但不能'a.b[2]'或者'a.b[1..2]',例如)

  • 对于作为哈希表的属性,请指定(文字)键名而不嵌入引号(例如,'a.b[1, 2]'); 请注意,您通常无法访问数字哈希表键,此外,您将无法通过索引访问有序哈希表的条目.

'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
    }
  }
Run Code Online (Sandbox Code Playgroud)

[1]验证(全部在一行)'a.ht[bar]',$co = New-Object PSCustomObject; Update-TypeData -TypeName System.Management.Automation.PSCustomObject -MemberType ScriptMethod -MemberName GetFoo -Value { 'foo' }; $co.GetFoo()即使foo$co调用之前创建了哪些输出.

  • @cyborg:它不适用于 _deserialized_ 自定义对象的原因是它们具有不同的类型名称:`Deserialized.System.Management.Automation.PSCustomObject` - 我已经更新了答案以向该类型添加 ETS 方法名也。 (2认同)