我有一个函数返回一个哈希表,我需要将键转换为嵌套对象,但我失去了理智。
这是我正在处理的哈希表的硬编码示例
# $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[$_])
}
任何帮助将不胜感激。
天啊!我一直在研究这个问题几个小时,但我认为我有一些有用的东西。我不得不使用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
]
}
}
}
}
我觉得这可以用更优雅的方式完成,但这是我能想到的(基于@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
]
}
}
对于具有递归的嵌套哈希表,这是一个非常简单的方法:
#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
}
}