我正在尝试动态解析&通过Powershell构建一些我要提供的传入JSON文件的数据结构(这将是非标准结构),然后处理这些文件中的数据。把它们交给下一步。
作为其中的一部分,我正在尝试将JSON文件的数据结构构建为一个数据路径列表,供我解析并通过&抓取数据,这样我就可以处理数组,嵌套的JSON对象等等。到目前为止一切都很好。
我陷入某种Powershell的特点是通过变量处理2个以上的深度级别。让我给你一个很好的代码块来演示这个问题......
# Generate a Quick JSON file with different data types & levels
[object]$QuickJson = @'
{
"Name" : "I am a JSON",
"Version" : "1.2.3.4",
"SomeBool" : true,
"NULLValue" : null,
"ArrayOfVersions" : [1.0,2.0,3.0],
"MyInteger" : 69,
"NestedJSON" : {
"Version" : 5.0,
"IsReady" : false
},
"DoubleNestedJSON" : {
"FirstLevel" : 1,
"DataValue" : "I am at first nested JSON level!",
"Second_JSON_Level" : {
"SecondLevel" : 2,
"SecondDataValue" : "I am on the 2nd nested level"
}
}
}
'@
# Import our JSON file into Powershell
[object]$MyPSJson = ConvertFrom-Json -InputObject $QuickJson
# Two quick string variables to access our JSON data paths
[string]$ShortJsonPath = "Name"
[string]$NestedJsonPath = "NestedJson.Version"
# Long string to access a double-nested JSON object
[string]$LongNestedJsonPath = "DoubleNestedJSON.Second_JSON_Level.SecondDataValue"
# Both of these work fine
Write-Host ("JSON Name (Direct) ==> " + $MyPSJson.Name)
Write-Host ("JSON Name (via Variable) ==> " + $MyPSJson.$ShortJsonPath)
# The following way to access a single nested Json Path works fine
Write-Host ("Nested JSON Version (via direct path) ==> " + $MyPSJson.NestedJson.Version)
# And THIS returns an empty line / is where I fall afoul of something in Powershell
Write-Host ("Nested JSON Version (via variable) ==> " + $MyPSJson.$NestedJsonPath)
# Other things I tried -- all returning an empty line / failing in effect
Write-Host ("Alternate Nested JSON Version ==> " + $($MyPSJson.$NestedJsonPath))
Write-Host ("Alternate Nested JSON Version ==> " + $MyPSJson.$($NestedJsonPath))
Write-Host ("Alternate Nested JSON Version ==> " + $($MyPSJson).$($NestedJsonPath))
# Similarly, while THIS works...
$MyPSJson | select-object -Property NestedJSON
# This will fail / return me nothing
$MyPSJson | select-object -Property NestedJSON.Version
...在对此进行一系列研究时,我遇到了将其转换为Hashtable的建议 - 但遗憾的是,这也有同样的问题。因此,使用上面的代码片段,以下内容将把JSON对象转换为哈希表。
# Same problem with a hash-table if constructed from the JSON file...
[hashtable]$MyHash = @{}
# Populate $MyHash with the data from our quickie JSON file...
$QuickJson | get-member -MemberType NoteProperty | Where-Object{ -not [string]::IsNullOrEmpty($QuickJson."$($_.name)")} | ForEach-Object {$MyHash.add($_.name, $QuickJson."$($_.name)")}
# ... and even then -- $MyHash."$($NestedJsonPath)" -- fails, while a single level deep string works fine in the variable! :(
所以很明显我遇到了Powershell内部逻辑问题的“某些东西”,但是我不能让Powershell在为什么这么做时过于乐于助人。添加“-debug”或类似物以试图增加冗长度并没有帮助阐明这一点。
我怀疑它类似于本文中提出的项目(https://blogs.technet.microsoft.com/heyscriptingguy/2011/10/16/dealing-with-powershell-hash-table-quirks/),但只是特定于变量。
我在Powershell语言规范中找不到任何明显的东西我没有任何运气(据我所知,3.0仍然是最新的 - https://www.microsoft.com/en-usdownload/details.aspx?id=36389)。它可能在那里,我可能会错过它。
任何关于如何让Powershell与之相配的建议都将非常感激。我不确定Powershell如何/为什么使用简单的字符串,但似乎在这里遇到'something.somethingelse'类型字符串的问题。
谢谢。
其他说明&原件附录:
似乎有几个问题需要攻击。一个是“处理单个嵌套级别”。对此的“快速修复”似乎是使用“Invoke-Expression”来解析语句,例如(重要 - 请注意第一个变量的后退!):
iex "`$MyPSJson.$NestedJsonPath"
使用Invoke-Expression也适用于多嵌套情况:
iex "`$MyPSJson.$LongNestedJsonPath"
提到的另一种方法是使用多个select语句......但是我无法使用多个嵌套对象(Powershell似乎由于某些原因无法正确解决这些问题)。 / p>
例如在这种情况下:
($MyComp | select $_.DoubleNestedJSON | select FirstLevel)
Powershell返回
FirstLevel
----------
...而不是实际的数据值。所以 - 现在,由于Powershell显然没有解析它们,所以选择似乎不适用于多级嵌套对象?
答案 0 :(得分:5)
当你写
之类的东西时$MyPSJson.Name
这将尝试从对象Name
中检索名为$MyPSJson
的成员。如果没有此类会员,您将获得$null
。
现在,当您使用成员名称的变量执行此操作时:
$MyPSJson.$ShortJsonPath
这非常相同,因为查找了存储在$ShortJsonPath
中的名称的成员并检索了其值。这里没有惊喜。
当您尝试使用不存在于对象上的成员时,例如
$MyPSJson.$NestedJsonPath
# equivalent to
# $MyPSJson.'NestedJSON.Version'
您将获得$null
,如前所述。 .
运算符只会访问由其左侧表达式导致的确切对象的成员。它将永远以您希望它的方式通过成员层次结构。坦率地说,我并不知道一种语言是这样运作的。
它与Invoke-Expression
一起使用的原因是,您有效地将$NestedJsonPath
字符串转换为表达式的一部分,从而导致:
$MyPSJson.NestedJSON.Version
然后Invoke-Expression
进行评估。
当然,您可以定义自己的功能(并且我更喜欢使用Invoke-Expression
而不是eval
,这个cmdlet应该很少(如果有的话)使用(heck,it& #39;对于PowerShell来说是eval
- 少数语言function Get-DeepProperty([object] $InputObject, [string] $Property) {
$path = $Property -split '\.'
$obj = $InputObject
$path | %{ $obj = $obj.$_ }
$obj
}
PS> Get-DeepProperty $MyPSJson NestedJson.Version
5,0
主张使用它)):
filter Get-DeepProperty([string] $Property) {
$path = $Property -split '\.'
$obj = $_
$path | %{ $obj = $obj.$_ }
$obj
}
PS> $MyPSJson | Get-DeepProperty nestedjson.version
5,0
你甚至可以把它变成一个过滤器,这样你就可以在管道上更自然地使用它了:
public enum Color
{
None = 0, Black = 1, ...
}
答案 1 :(得分:1)
当您在字符串中提供您想要的属性时,例如
[string]$NestedJsonPath = "NestedJson.Version"
Powershell会查找名为NestedJSon.Version
的属性。它实际上并不遍历属性,而是查找包含句点的字符串文字。事实上,如果我像这样添加这样的属性到你的JSON。
[object]$QuickJson = @'
{
"Name" : "I am a JSON",
"Version" : "1.2.3.4",
"SomeBool" : true,
"NULLValue" : null,
"ArrayOfVersions" : [1.0,2.0,3.0],
"MyInteger" : 69,
"NestedJSON.Version" : 69,
"NestedJSON" : {
"Version" : 5.0,
"IsReady" : false
}
}
我现在得到一个价值,如下:
>$MyPSJson.$NestedJsonPath
69
获得价值的最佳方法是使用两个单独的变量,如下所示。
$NestedJson = "NestedJson"
$property = "Version"
>$MyPSJson.$NestedJson.$property
5.0
或者,您也可以使用select语句,如下面的原始答案所示。
$MyPSJson | select $_.NestedJSON | select Version
Version
-------
1.2.3.4
如果您使用多个Select-Object语句,它们将丢弃其他属性,并允许您更轻松地深入查看您想要的值。
答案 2 :(得分:1)
我遇到了同样的问题,所以我写了一个能解决问题的函数。 它可以通过变量路径(字符串)访问json的任何级别:
function getNestedJsonValue() {
param(
[Parameter(Mandatory = $true, ValueFromPipeline)] [PSCustomObject] $inputObj,
[Parameter(Mandatory = $true)] [string] $valuePath
)
if (($valuePath -eq $null) -or ($valuePath.length -eq 0) -or ($inputObj -eq $null)) {
return $inputObj
}
[System.Array] $nodes = "$valuePath" -split '\.'
foreach ($node in $nodes) {
if (($node -ne $null) -and ($node.length -gt 0) -and ($inputObj -ne $null)) {
$inputObj = $inputObj.$node
} else {
return $inputObj
}
}
return $inputObj
}
getNestedJsonValue -valuePath $nestedValuePath -inputObj $someJson
$someJson | getNestedJsonValue -valuePath $nestedValuePath
$nestedValuePath="some.nested.path"
答案 3 :(得分:0)
感谢wOxxOm让事情走上正轨。
Invoke-Expression似乎对这种情况很有效(如果有些昂贵,但在我的个人示例和情况下这很好),它可以应对多层嵌套。
所以作为上面代码片段的示例,以下内容将解决得很好(关键点 - 注意最初的反击。这让我措手不及):
Write-Host ("Single level JSON test ==> " + (iex "`$MyPSJson.$NestedJsonPath"))
Write-Host ("Double level JSON test ==> " + (iex "`$MyPSJson.$LongNestedJsonPath"))
这将返回我们期望的结果:
Single level JSON test ==> 5.0
Double level JSON test ==> I am on the 2nd nested level
FoxDeploy使用多级选择的答案似乎不适用于2级以上的嵌套,遗憾的是出于某些奇怪的原因。
使用:
($MyPSJson | select $_.DoubleNestedJSON | select FirstLevel)
我们从Powershell获得以下回复:
FirstLevel
----------
... 似乎,Powershell并不能完全解析嵌套对象?如果我们故意使用不存在的东西,我们会得到类似的结果:
($MyPSJson | select $_.DoubleNestedJSON | select Doesnotexist)
...也简单地回复:
Doesnotexist
------------
所以 - 现在 - 似乎“ Invoke-Expression ”最可靠地运行(并且最容易,因为它只是将变量与路径字符串一起交给它)。
到目前为止,我仍然无法解释任何此类的为什么(因为我通过阵列非常愉快地使用了'dotwalk'-ing与多个变量),但至少有一个解决方案现在......那就是调用表达式!
到目前为止我发现的调用表达式的最佳(/最不好?)解释是在这里(微软自己对cmdlet的描述并不能很好地暗示它在这种情况下帮忙:
答案 4 :(得分:0)
我遵循了Joey的过滤器示例。但是,我发现它不支持访问数组。 共享我为此工作的代码。希望它也会对其他人有所帮助。很棒的线程!
filter Get-DeepProperty([string] $Property) {
$path = $Property -split '\.'
$obj = $_
foreach($node in $path){
if($node -match '.*\[\d*\]'){
$keyPieces = $node -split ('\[')
$arrayKey = $keyPieces[0]
$arrayIndex = $keyPieces[1] -replace ('\]','')
$obj = $obj.$arrayKey[$arrayIndex]
} else {
$obj = $obj.$node
}
}
$obj
}
用法示例:
$path = "nested.nestedtwo.steps[2]"
$payload | Get-DeepProperty $path