通过Powershell

时间:2017-01-26 14:09:04

标签: json powershell

我正在尝试动态解析&通过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显然没有解析它们,所以选择似乎不适用于多级嵌套对象?

5 个答案:

答案 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将为$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