使用递归将哈希表转换为对象

时间:2016-03-11 18:48:31

标签: powershell

我有一个返回哈希表的函数,我需要将键转换为嵌套对象,但我正在失去理智。

这是我正在处理的哈希表的硬编码示例

# $hash = SomeFunctionThatReturnsAhashTable

$hash = @{
    'root.Blubb' = @(5)
    'root.controller.haha' = 'hoho', 'hoho'
    'root.controller.hugo' = @(12)
    'root.controller.name' = '10.3.3.171', '10.3.3.172'
    'root.controller.renate' = @(123)
    'root.controller.test' = 2, 2
    'root.controller.upsala' = @('handy')
    'root.t.t1.wert' = @(1)
    'root.t.t2.wert' = @(2)
    'root.test' = 1, 2
}

以下是我想要将哈希表转换为

的想法
$obj = [pscustomobject]@{
    root = [pscustomobject]@{
        Blubb = @(5)
        controller = [pscustomobject]@{
            haha = 'hoho', 'hoho'
            hugo = @(12)
            name = '10.3.3.171', '10.3.3.172'
            renate = @(123)
            test = 2, 2
            upsala = @('handy')
        }
        t = [pscustomobject]@{
            t1 = [pscustomobject]@{
                wert = @(1)
            }
            t2 = [pscustomobject]@{
                wert = @(2)
            }
        }
        test = 1, 2
    }
}

我正试图分开'。'并返回子对象,但我不知道如何完成它。如果有更好的方法可以解决这个问题,请告诉我。这是我到目前为止所做的。

function keytoobject ($key, $value) {
    if ($key.contains('.')) {
        [pscustomobject]@{
            ($key.substring($key.indexof('.')+1)) = (keytoobject $key.substring($key.indexof('.')+1) $value)
        }
    } else {
        [pscustomobject]@{
            $key = $value
        }
    }
}

$hash.Keys | % {
    keytoobject $_ ($hash[$_])
}

任何帮助将不胜感激。

3 个答案:

答案 0 :(得分:4)

哦,天啊!我一直在研究这个问题几个小时,但我认为我有一些有用的东西。我不得不使用Add-Member比我想要的更多,但这是我创建空对象的方式,因此它们不等于$null。这很重要,因为需要创建使用新嵌套对象确定的测试。

function Add-NestedObject($sourceObject, $path, $objectData){
    # This function will add the object $objectToNest into $sourceObject into the location named by $parentPath
    $currentPath,$remainingPath = $path.Split(".",2)

    # Check to see if the object contains the following subproperty.
    if($sourceObject.$currentPath -eq $null){
        # This property does not exist and needs to be created. Use an empty object
        Add-Member -Name $currentPath -TypeName PSObject -InputObject $sourceObject -MemberType NoteProperty -Value (New-Object -TypeName PSObject)
    }

    # Are there more elements to this path?
    if($remainingPath){
        # There are more nested objects. Keep passing data until we get to the point where we can populate it.  
        Add-NestedObject ($sourceObject.$currentPath) $remainingPath $objectData
    } else {
        # Now we can use the data and populate this object.
        $props = @{} 
        $objectData | ForEach-Object{
            $_.Name = $_.Name.Split(".")[-1]
            $props.($_.Name) = $_.Value
        }

        # Set the current path in the object to contain the data we have been passing. 
        $sourceObject.$currentPath = [pscustomobject]$props
    }
}


$schema = $hash.GetEnumerator() | 
    Select-Object Name,Value,@{Name="Parent";Expression={$split = $_.Name -split "\.";$split[0..($split.Count - 2)] -join "."}} | 
    Group-Object Parent | Sort-Object Name

# Empty Object to start
$object = New-Object -TypeName PSObject 

# Build the object skeleton
$schema | ForEach-Object{Add-NestedObject $object $_.Name $_.Group}

# Show the monstrosity
$object

基础是我们使用组对象将所有值收集到父属性中。对于每个父属性,我们使用递归函数创建路径中的每个节点(假设它尚不存在)。一旦我们创建了所有节点,我们就可以将值集合放在该节点中。

将值集合重建为自定义对象并分配给结束节点。

以下是JSON的内容,因此您可以看到对象在转换后的样子。

{
    "root":  {
                 "test":  [
                              1,
                              2
                          ],
                 "Blubb":  [
                               5
                           ],
                 "controller":  {
                                    "name":  [
                                                 "10.3.3.171",
                                                 "10.3.3.172"
                                             ],
                                    "haha":  [
                                                 "hoho",
                                                 "hoho"
                                             ],
                                    "hugo":  [
                                                 12
                                             ],
                                    "test":  [
                                                 2,
                                                 2
                                             ],
                                    "upsala":  [
                                                   "handy"
                                               ],
                                    "renate":  [
                                                   123
                                               ]
                                },
                 "t":  {
                           "t1":  {
                                      "wert":  [
                                                   1
                                               ]
                                  },
                           "t2":  {
                                      "wert":  [
                                                   2
                                               ]
                                  }
                       }
             }
}

答案 1 :(得分:3)

我觉得这可以用更优雅的方式完成,但这是我能想到的(基于@mjolinor's previous answer on SO)。

我们的想法是创建一个包含所有必需级别的哈希表树,然后将值插入它们应该的位置(这在哈希表中比在对象中更容易),最后但并非最不重要:将哈希表转换为PSCustomObject。就像问题中的样本/绘图一样。

#Don't mind the sexy function-name
function ConvertDelimitedHashtableTo-NestedObject ([hashtable]$Hash) {

    #Hashtable to store data in
    $result = @{}

    #iex = Invoke-Expression
    #It can execute a command stored in a string.
    #It's necessary because we don't know the path before runtime (since paths depends on the inputdata).

    #Design skeleton (get path to every "parent node"/hashtable/object)
    $paths = $hash.Keys |
    #Only "delimited" keys will require a hashtable/subobject (without this, $hash = @{ 'hello' = 'world' } would fail)
    Where-Object { $_ -match '\.' } | ForEach-Object { 
        #Split string into nodes
        $parts = $_.split(".")
        0..($parts.count -2) | Foreach-Object {
            #Get every node-path except deepest level (value-node/property)
            "`$result.$($parts[0..$_] -join '.')"
        }
    } |
    #Remove duplicates
    Select-Object -Unique |
    #Sort by number of levels (because we can't create root.t before root exists)
    Sort-Object {@($_.ToCharArray() -eq '.').Count}


    #Create skeleton
    $paths | ForEach-Object {
        #Creating hashtable for each level (except values-nodes) to get a complete skeleton/tree
        iex "$_ = @{}"
    }

    #Insert values
    $hash.Keys | ForEach-Object {
        #Add values/properties to the correct hashtable with value from the input-hashtable
        iex "`$result.$_ = `$hash['$_']"
    }

    #Convert each hashtable-node to PSCustomObject
    $paths | ForEach-Object {
        iex "$_ = [pscustomobject]$_"
    }

    #Output main-hashtable as PSCustomObject
    [pscustomobject]$result
} 


#Original object
$myht = @{
    'root.Blubb' = @(5)
    'root.controller.haha' = 'hoho', 'hoho'
    'root.controller.hugo' = @(12)
    'root.controller.name' = '10.3.3.171', '10.3.3.172'
    'root.controller.renate' = @(123)
    'root.controller.test' = 2, 2
    'root.controller.upsala' = @('handy')
    'root.t.t1.wert' = @(1)
    'root.t.t2.wert' = @(2)
    'root.test' = 1, 2
}

$obj = ConvertDelimitedHashtableTo-NestedObject -Hash $myht

这会生成并执行以下代码(我从脚本中删除了iex,因此它只输出每个生成的代码行):

#Manually created main hashtable: $result = @{}
#Create hashtable-skeleton
$result.root = @{}
$result.root.controller = @{}
$result.root.t = @{}
$result.root.t.t2 = @{}
$result.root.t.t1 = @{}
#Insert values
$result.root.controller.test = $hash['root.controller.test']
$result.root.controller.upsala = $hash['root.controller.upsala']
$result.root.controller.renate = $hash['root.controller.renate']
$result.root.t.t2.wert = $hash['root.t.t2.wert']
$result.root.test = $hash['root.test']
$result.root.controller.name = $hash['root.controller.name']
$result.root.controller.haha = $hash['root.controller.haha']
$result.root.Blubb = $hash['root.Blubb']
$result.root.t.t1.wert = $hash['root.t.t1.wert']
$result.root.controller.hugo = $hash['root.controller.hugo']
#Cast hashtables to objects
$result.root = [pscustomobject]$result.root
$result.root.controller = [pscustomobject]$result.root.controller
$result.root.t = [pscustomobject]$result.root.t
$result.root.t.t2 = [pscustomobject]$result.root.t.t2
$result.root.t.t1 = [pscustomobject]$result.root.t.t1
#Manually casted main hashtable to object: $obj = [pscustomobject]$result

并为您提供此对象(使用Format-Custom显示整个树):

$obj | Format-Custom

class PSCustomObject
{
  root = 
    class PSCustomObject
    {
      t = 
        class PSCustomObject
        {
          t1 = 
            class PSCustomObject
            {
              wert = 
                [
                  1
                ]

            }
          t2 = 
            class PSCustomObject
            {
              wert = 
                [
                  2
                ]

            }
        }
      Blubb = 
        [
          5
        ]

      controller = 
        class PSCustomObject
        {
          name = 
            [
              10.3.3.171
              10.3.3.172
            ]

          haha = 
            [
              hoho
              hoho
            ]

          hugo = 
            [
              12
            ]

          test = 
            [
              2
              2
            ]

          upsala = 
            [
              handy
            ]

          renate = 
            [
              123
            ]

        }
      test = 
        [
          1
          2
        ]

    }
}

答案 2 :(得分:0)

这是一种具有递归的嵌套哈希表的非常简单的方法:

#Example hash
$obj = @{A="B";c=@{D="F";g="H"}}


# the function
function Get-HashAsObject
{
    param ([hashtable]$hash, [switch]$Deep)

    $NewHash = @{}
    foreach ($k in $hash.Keys)
    {
        if ($hash[$k] -is [hashtable] -and $Deep)
        {
            $NewHash.Add($k,(Get-HashAsObject -Deep -hash $hash[$k]))
        }
        else
        {
            $NewHash.Add($k,$hash[$k])
        }
    }
    return [PSCustomObject]$NewHash
}

"Shallow"
$s = Get-HashAsObject $obj 
$s | fc
"Deep"
$d = Get-HashAsObject $obj -Deep
$d | fc

输出:

class PSCustomObject
{
  A = B
  c = 
    [
      class DictionaryEntry
      {
        Key = D
        Value = F
        Name = D
      }
      class DictionaryEntry
      {
        Key = g
        Value = H
        Name = g
      }
    ]

}

class PSCustomObject
{
  A = B
  c = 
    class PSCustomObject
    {
      D = F
      g = H
    }
}