如何将PowerShell哈希表转换为对象?

3

如果使用PSCustomObject而不是导入Import-PowerShellDataFile等PowerShell中的某些哈希表,则导航会更加容易。

@{
    AllNodes = @(
        @{
            NodeName = 'SRV1'
            Role = 'Application'
            RunCentralAdmin = $true
        },
        @{
            NodeName = 'SRV2'
            Role = 'DistributedCache'
            RunCentralAdmin = $true
        },
        @{
            NodeName = 'SRV3'
            Role = 'WebFrontEnd'
            PSDscAllowDomainUser = $true
            PSDscAllowPlainTextPassword = $true
            CertificateFolder = '\\mediasrv\Media'
        },
        @{
            NodeName = 'SRV4'
            Role = 'Search'
        },
        @{
            NodeName = '*'
            DatabaseServer = 'sql1'
            FarmConfigDatabaseName = '__FarmConfig'
            FarmContentDatabaseName = '__FarmContent'
            CentralAdministrationPort = 1234
            RunCentralAdmin = $false
        }
    );
    NonNodeData = @{
        Comment = 'No comment'
    }
}

当导入时,它将成为哈希表的哈希表。
$psdnode = Import-PowerShellDataFile .\nodefile.psd1

$psdnode

Name                           Value
----                           -----
AllNodes                       {System.Collections.Hashtable, System.Collect...
NonNodeData                    {Comment}

$psdnode.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Hashtable                                System.Object

当通过属性名称导航时,数据结构会变得很奇怪。


1
“当通过属性名称导航时,数据结构会变得非常奇怪。” - 你能告诉我们更多关于你的意思吗?字典(包括哈希表)通常比具有动态成员集的对象更容易迭代/遍历。 - Mathias R. Jessen
@MathiasR.Jessen 这取决于您的个人喜好 :) - Dennis
1
你确定吗?PowerShell有一个字典适配器,将键公开为属性,因此您可以像自定义对象一样查询它:foreach($node in $psdnode.AllNodes){ Write-Host $node.NodeName } :) - Mathias R. Jessen
2
一旦哈希表被转换为自定义对象,您想要做什么?您会编写什么代码? - Mathias R. Jessen
1
这就是我想告诉你的:你已经可以使用哈希表来实现这个了 :) $psdnode.AllNodes |Where-Object NodeName -eq SRV1 已经可以 与你提供的数据一起工作。 - Mathias R. Jessen
显示剩余8条评论
3个回答

11
现有答案中有很多有用的信息,但考虑到你的问题的普遍性,让我来试试一个系统的概述:
你不需要将一个hashtable转换为[pscustomobject]实例,就可以使用点符号来深入到它的条目(属性)中,正如评论中所讨论的并在iRon's answer中展示的那样。
一个简单的例子:
@{ top = @{ nested = 'foo' } }.top.nested  # -> 'foo'

更多信息请参见this answer
实际上,在可能的情况下,使用hashtables比使用[pscustomobject]更可取,因为:
  • 它们比[pscustomobject]实例更轻量级(占用更少的内存)
  • 更容易迭代构建它们,并根据需要添加/删除条目。
注意:
- 上述内容不仅适用于[hashtable]类型,而且更普遍地适用于实现[System.Collections.IDictionary]接口或其泛型对应项System.Collections.Generic.IDictionary[TKey, TValue]]的类型的实例,特别是包括有序哈希表(这些是System.String类型的实例,PowerShell允许您使用语法糖[ordered] @{ ... }来构造)。
- 除非另有说明,下文中的hashtable指的是所有这些类型。
在需要将一个[hasthable]转换为[pscustomobject]的情况下:
虽然许多标准的cmdlets可以将[hasthable]与[pscustomobjects]互换使用,但有些不能,特别是ConvertTo-Csv和Export-Csv(请参阅GitHub问题#10999以请求更改此功能);在这种情况下,必须进行转换为[pscustomobject]。
注意:Hasthables可以具有任何类型的键,而转换为[pscustomobject]则必须使用字符串"键",即属性名称。因此,并非所有的hastables都可以忠实或有意义地转换为[pscustomobject]。
  • 将非嵌套的哈希表转换为 [pscustomobject]:

    • PowerShell 提供了一种语法糖,用于 [pscustomobject] 字面量(例如,[pscustomobject] @{ foo = 'bar'; baz = 42 }),也可以通过现有的哈希表实现,例如:

      $hash = @{ foo = 'bar'; baz = 42 } 
      $custObj = [pscustomobject] $hash   # 简单地转换为 [pscustomobject]
      
  • 将嵌套的哈希表,即对象图,转换为 [pscustomobject] 图:

    • 一种简单但有限且潜在昂贵的解决方案是在 您自己的答案 中所示的方法:使用 ConvertTo-Json 将哈希表转换为 JSON,然后使用 ConvertFrom-Json 将生成的 JSON 转换回 [pscustomobject]

      • 除了性能问题之外,这种方法的根本限制是可能丢失类型的准确性,因为 JSON 仅支持少数数据类型。虽然使用 Import-PowerShellDataFile 读取的哈希表不会有问题,但给定的哈希表可能包含在 JSON 中没有有意义表示的类型的实例。
    • 您可以使用一个自定义转换函数 ConvertFrom-HashTable(下面是源代码)来克服这个限制;例如(使用 Format-Custom -InputObject $custObj 检查结果):

      $hash = @{ foo = 'bar'; baz = @{ quux = 42 } } # 嵌套的哈希表
      $custObj = $hash | ConvertFrom-HashTable # 转换为 [pscustomobject] 图
      

ConvertFrom-HashTable 源代码:

注意:尽管名称如此,该函数通常支持实现 IDictionary 接口的类型实例作为输入。

function ConvertFrom-HashTable {
  param(
    [Parameter(Mandatory, ValueFromPipeline)]
    [System.Collections.IDictionary] $HashTable
  )
  process {
    $oht = [ordered] @{} # Aux. ordered hashtable for collecting property values.
    foreach ($entry in $HashTable.GetEnumerator()) {
      if ($entry.Value -is [System.Collections.IDictionary]) { # Nested dictionary? Recurse.
        $oht[$entry.Key] = ConvertFrom-HashTable -HashTable $entry.Value
      } else { # Copy value as-is.
        $oht[$entry.Key] = $entry.Value
      }
    }
    [pscustomobject] $oht # Convert to [pscustomobject] and output.
  }
}

1
哇,非常好的观点和解释 :) - Dennis
很高兴听到这个好消息,@Dennis。 - mklement0
1
你知道吗,我在寻找一个可以作为参考来源来支持我自己发现的Hashtable > Json > Json > PSCustomObject技巧(我称之为Json嘿呀技巧)时,找到了这个页面。 - Jeremy Bradshaw

3

问题是什么?

@'
@{
    AllNodes = @(
        @{
            NodeName = 'SRV1'
            Role = 'Application'
            RunCentralAdmin = $true
        },
        @{
            NodeName = 'SRV2'
            Role = 'DistributedCache'
            RunCentralAdmin = $true
        },
        @{
            NodeName = 'SRV3'
            Role = 'WebFrontEnd'
            PSDscAllowDomainUser = $true
            PSDscAllowPlainTextPassword = $true
            CertificateFolder = '\\mediasrv\Media'
        },
        @{
            NodeName = 'SRV4'
            Role = 'Search'
        },
        @{
            NodeName = '*'
            DatabaseServer = 'sql1'
            FarmConfigDatabaseName = '__FarmConfig'
            FarmContentDatabaseName = '__FarmContent'
            CentralAdministrationPort = 1234
            RunCentralAdmin = $false
        }
    );
    NonNodeData = @{
        Comment = 'No comment'
    }
}
'@ |Set-Content .\nodes.psd1

$psdnode = Import-PowerShellDataFile .\nodefile.psd1

$psdnode

Name                           Value
----                           -----
NonNodeData                    {Comment}
AllNodes                       {SRV1, SRV2, SRV3, SRV4…}

$psdnode.AllNodes.where{ $_.NodeName -eq 'SRV3' }.Role
WebFrontEnd

2
我昨天刚发现的一种非常简单的方法是通过JSON进行“双转换”。
$nodes = Import-PowerShellDataFile .\nodes.psd1 | ConvertTo-Json | ConvertFrom-Json

$nodes

AllNodes
--------
{@{NodeName=SRV1; RunCentralAdmin=True; Role=Application}, @{NodeName=SRV2; RunCentralAdm...}

$nodes.GetType()   

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     False    PSCustomObject                           System.Object

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接