Powershell 使用递归将哈希表转换为对象
我有一个返回哈希表的函数,我需要将键转换成嵌套对象,但我正在失去理智 下面是我正在处理的哈希表的一个硬编码示例Powershell 使用递归将哈希表转换为对象,powershell,Powershell,我有一个返回哈希表的函数,我需要将键转换成嵌套对象,但我正在失去理智 下面是我正在处理的哈希表的一个硬编码示例 # $hash = SomeFunctionThatReturnsAhashTable $hash = @{ 'root.Blubb' = @(5) 'root.controller.haha' = 'hoho', 'hoho' 'root.controller.hugo' = @(12) 'root.controller.name' = '10.3.
# $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[$_])
}
任何帮助都将不胜感激。我感觉这可以用一种更优雅的方式来完成,但这是我所能想到的(基于) 其思想是创建一个具有所有所需级别的哈希表树,然后将值插入它们应该位于的位置(在哈希表中比在对象中更容易实现),最后但并非最不重要的一点是:将哈希表强制转换为
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
,因此它只输出生成的每一行代码):
并为您提供此对象(使用格式自定义
显示整个树):
天哪!我已经为此工作了几个小时,但我想我有一些东西正在工作。我不得不使用
addmember
,但这是我创建空对象的方法,因此它们不等于$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
]
}
}
}
}
下面是一种非常简单的递归嵌套哈希表方法:
#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
}
]
}
Deep
class PSCustomObject
{
A = B
c =
class PSCustomObject
{
D = F
g = H
}
}
在这个场景中,
root.controller
是否有一个值?我这样问是因为它可能是一个复杂的问题,应该解决如何处理它。期望的结果是root.controller的值将是一个具有6个属性的单个对象,如所提供代码的第二部分所示。我的意思是尽可能好的:“为什么有这样的数据格式”?废话。。。我几乎回答完了。希望它足够不同。干得好,谢谢。迫不及待地想看看你有什么想法。在我开始工作之前,我必须重新开始1,2…5次。:-)哦,我的上帝,这件事让我秃顶了。我刚刚完成了物体的骨架。现在只是填充它…显示这些类型的东西非常有用。尽管我在这里没有“赢”,我还是更新了我的答案。经过几个小时的睡眠后,我想我可以简化很多我很自豪,再也不想看到这一切了。祝贺你。我的前4次尝试是使用递归,但我找不到阻止它的方法。不确定我更喜欢哪种方法,但至少这更容易阅读。:-)我差点放弃这一点,更倾向于使用调用表达式。我想这是一般方法3第6版哇@弗罗德夫,马特,你们绝对,毫无疑问是令人惊奇的。如果我能标出两个答案,我会的。在看到这一切之后,我意识到我在这件事上远远超出了我的深度。我知道我只是互联网上的无名小卒,但说真的,这些都是最令人印象深刻的。我之所以选择这一个作为答案,主要是因为我个人觉得在powershell中使用iex和哈希表不太舒服。谢谢你们两位的知识,没问题。我现在把我的答案简化了一点,这样就不会那么难理解了Invoke Expression
像普通用户一样运行命令,但由于我们还不知道要运行的代码(因为每次$hash
可能不同),所以我们动态创建它。要理解代码的作用,只需从脚本中删除iex
,所有行都将打印到屏幕上,以便您可以看到生成对象的实际脚本。您会注意到,它正是您在示例中描述的:使用值创建嵌套哈希表,然后将它们强制转换为pscustomobject
:-)谢谢你的帮助!运行之后,我无法执行类似于$d.root
的操作来查看该级别的所有属性。当我执行$d|converttojson
时,输出与我看到的matt或frode的结果不匹配
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
}
}