【问题标题】:Problems parsing / accessing nested JSON / Hashtable data via variables in Powershell通过 Powershell 中的变量解析/访问嵌套 JSON/Hashtable 数据时出现问题
【发布时间】:2017-01-26 14:09:03
【问题描述】:

我正在尝试通过 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 的建议——但遗憾的是,这也有同样的问题。所以用上面的code-sn-p,下面会把JSON对象转换成hashtable。

# 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"

提到的另一种方法是使用多个选择语句......但我无法让它与多嵌套对象一起使用(Powershell 似乎由于某种原因无法正确解决这些问题)。

例如在这种情况下:

($MyComp | select $_.DoubleNestedJSON | select FirstLevel)

Powershell 返回

FirstLevel    
---------- 

... 而不是实际的数据值。所以 - 目前看来,由于 Powershell 显然没有解决它们,因此选择似乎不适用于多级嵌套对象?

【问题讨论】:

  • Invoke-Expression 是最简单(我猜也是最慢)的解决方案:iex "`$MyPSJson.$NestedJsonPath"
  • 我可以忍受慢 - 只要我能让它表现/开始工作。
  • 仍然没有更接近理解 iex 为何有效,但 dot-walking 无效……但只想说(单独)非常感谢您让我摆脱这种束缚。您的建议完美适用于多层嵌套。 :)

标签: json powershell


【解决方案1】:

我遵循 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

【讨论】:

    【解决方案2】:

    当你写类似的东西时

    $MyPSJson.Name
    

    这将尝试从对象$MyPSJson 中检索名为Name 的成员。如果没有这样的成员,你会得到$null

    现在,当您使用成员名称的变量执行此操作时:

    $MyPSJson.$ShortJsonPath
    

    这几乎相同,因为名称存储在$ShortJsonPath 中的成员被查找并检索其值。这里没有惊喜。

    当您尝试使用对象上不存在的成员,例如

    $MyPSJson.$NestedJsonPath
    # equivalent to
    # $MyPSJson.'NestedJSON.Version'
    

    您将收到$null,如前所述。 . 运算符只会访问作为其左侧表达式结果的确切对象的成员。它永远不会以您期望的方式通过成员层次结构。坦率地说,我不知道有一种语言可以这样工作。

    它与Invoke-Expression 一起工作的原因是,您有效地将$NestedJsonPath 字符串转换为表达式的一部分,从而导致:

    $MyPSJson.NestedJSON.Version
    

    Invoke-Expression 然后进行评估。

    当然,您可以定义您自己的以这种方式工作的函数(我更喜欢这样而不是使用 Invoke-Expression,这个 cmdlet 应该很少使用(如果有的话)(见鬼,它是 eval 用于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
    

    【讨论】:

    • 啊 - 好吧,这很有意义,这么说吧。非常感谢(主要是脚本专家而不是程序员,点符号对我来说似乎不是问题)。使用过滤器也不是我想过的。这里非常有前途和有趣的建议。我理解使用 Invoke-Expression 的犹豫 - 没有狡辩。我的首要任务是从“让该死的东西开始工作”开始。我非常喜欢您使用过滤器的变体方法。另外(再次)非常感谢您首先了解为什么这是一个问题:)。
    【解决方案3】:

    为什么这不起作用

    当你在字符串中提供你想要的属性时,像这样

    [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 语句,它们将丢弃其他属性,并允许您更轻松地向下钻取到您想要的值。

    【讨论】:

    • 有趣......似乎Powershell正在以一种非常......“奇怪”的方式处理某些对象。 Invoke-Expression 路由适用于单层嵌套。我玩过一个双重嵌套的 JSON 文件,这两者都有问题 - “调用表达式”以及建议的多级选择方法。我再深入研究一下,你们给了我一些想法。我还将编辑 JSON sn-p 和问题描述以获取更多详细信息,因为它是有意义的!感谢到目前为止的所有想法。 :)
    • 好的 - 所以,Powershell 似乎在处理嵌套对象真的很奇怪。虽然您的建议似乎适用于单级嵌套,但它似乎在双嵌套时分崩离析...... PS 只返回属性,但不返回数据值。我已经用新信息编辑了我的问题。然而,Invoke-Expression does 可以解决问题,即使是通过多层嵌套(不知道为什么)。如果 PS 能给出更多关于原因的说明,那就太好了,但是哦,好吧。所以 - 现在,我将把“iex”路由作为答案,因为它也适用于多级嵌套。
    • 请注意,您的select 管道元素无法按预期工作,除非您位于实际定义了$_ 的脚本块中。也许你的意思是select NestedJSON
    • 好点乔伊,在他的例子中,我们每个循环都在一个大范围内。
    • 啊,我已经更新了答案。很棒的 Steven Murawski 看了看,帮助我了解发生了什么。
    【解决方案4】:

    我遇到了同样的问题,所以我写了一个函数来解决这个问题。 它允许通过变量路径(字符串)访问任何级别的 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"

    【讨论】:

    • 噢——非常好。我将试一试(目前主要使用 python 来处理 JSON-s)。谢谢 - 感谢分享:)。
    • *做了一个小改动,以修复不返回嵌套元素的 $null 值(而是返回父级的值)的行为。
    【解决方案5】:

    感谢 wOxxOm 让事情走上正轨。

    Invoke-Expression 似乎非常适合这种情况(如果有点贵,但在我个人的示例和情况下这很好),它可以应对多层次的嵌套。

    因此,作为上述代码 sn-p 的示例,以下将很好地解决(关键点 - 注意最初的反引号。这让我措手不及):

    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” 工作最可靠(并且最容易,因为它只是将带有路径字符串的变量传递给它的情况)。

    到目前为止,我仍然无法解释 WHY 的任何原因(因为我已经非常高兴地通过数组使用了带有多个变量的“dotwalk”),但至少有一个解决方案现在......那就是Invoke-Expression

    到目前为止,我发现的 Invoke-Expression 的最佳(/最差?)解释在这里(Microsoft 自己对 cmdlet 的描述并不能很好地暗示它在这种情况下会有所帮助):

    【讨论】:

    • 查看我的最新答案和 Joey 的答案,了解原因。
    • 已经完成了。给你们两个很棒的小费。清楚地说明我在哪里犯规以及为什么。还给了我一些关于如何攻击它的选项。我现在会再玩一些,因为我不确定有多少层嵌套对象会朝着我的方向前进——所以一个函数或过滤器方法让我觉得更安全。但是,是的 - 在这两个方面都非常有帮助。非常感谢你为我清理了我的“duh”:)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-24
    • 1970-01-01
    • 2023-03-16
    相关资源
    最近更新 更多