PowerShell JSON:如何找出,如果密钥存在=

PowerShell JSON: How to find out, if key exists=

提问人:am2 提问时间:11/17/2023 更新时间:11/17/2023 访问量:50

问:

我使用 Powershell 5.1 我尝试使用(简单的)JSON文件作为脚本之间的信箱。 因此,在脚本的开头,我读取了文件,对其进行了修改,并将其存储在末尾。 例如,以下文件

{
    "A":  {
              "April":  "4",
              "August":  "8"
          },
    "J":  {
              "January":  "1",
              "July":  "7"
          }
}

为了更好地处理,我编写了一个 Read-Value 和一个 Write-Value 函数。在此示例中,Read-Value 显示了我的问题:该函数找不到参数“Name”。

$config_file_full = Join-Path -Path $PSScriptRoot -ChildPath 'months.json'
$config_json = Get-Content -Raw -Path $config_file_full | ConvertFrom-Json

function Read-Value {
    param (
        [string]$Subject,
        [string]$Name
    )
    # $result = $null

    if (-not ($config_json.PSObject.Properties.Name -contains $Subject)) {
        Write-Host "READ NO MATCH for SUBJECT " $Subject
        $result = $null
    } elseif (-not ($config_json.PSObject.Properties.$Subject.Name -contains $Name)) {
        Write-Host "READ NO MATCH FOR SUBJECT " $Subject "NAME " $Name
        $result = $null
    } else {
        $result = $config_json.$Subject.$Name
        Write-Host "READ NO MATCH FOR SUBJECT " $Subject "NAME " $Name "VALUE <" $result ">"
    }

    return $result
}

$a = Read-Value -Subject 'A' -Name 'April'
Write-Host "<" $a ">"

$config_json | ConvertTo-Json | Set-Content -Path $config_file_full

结果是

READ NO MATCH FOR SUBJECT  A NAME  April
<  >

怎么了?elseif- 条件未按预期工作。 谢谢

JSON PowerShell

评论

1赞 James Ruskin 11/17/2023
那里有一些有趣的部分。你可能会发现这一行效果更好:这在某种程度上归因于PSNoteProperty不能像那样使用点表示法(有趣的是,它更接近于哈希表而不是一组命名属性),然后是其中的“值”是一个对象 - 所以即使点表示法起作用,你检查的级别也太高了(检查名称为“A”的“April”)。} elseif (-not ($config_json.PSObject.Properties[$Subject].Value.PSObject.Properties.Name -contains $Name)) {
0赞 am2 11/17/2023
这似乎有效,即使我不知道这里发生了什么......将其封装到可读函数中是我能做的最好的事情。谢谢你的回答。做一个答案,这样我就可以提交它

答:

2赞 James Ruskin 11/17/2023 #1

这里有几个问题。

  • 此处的 PSObject.Properties 是实例的集合(由于@mklement0已更正),这些实例不允许你尝试使用点表示法对每个属性进行寻址。PSPropertyInfo
  • 即使您到达要检查的 Subject,该属性也包含该 Subject 的名称,而不是您正在检查的名称。$SubjectName$Name

这应该效果更好:

function Read-Value {
    param (
        [string]$Subject,
        [string]$Name
    )
    if (-not ($config_json.PSObject.Properties.Name -contains $Subject)) {
        Write-Host "READ: No match for SUBJECT '$($Subject)'"
    } elseif (-not ($config_json.PSObject.Properties[$Subject].Value.PSObject.Properties.Name -contains $Name)) {
        Write-Host "READ: Matched Subject '$($Subject)' but not Name '$($Name)'"
    } else {
        Write-Host "READ: Matched Subject '$($Subject)' and Name '$($Name)': <$($config_json.$Subject.$Name)>"
        $config_json.$Subject.$Name
    }
}
3赞 Mathias R. Jessen 11/17/2023 #2

James Ruskin 的回答将解决您的问题,但如果您用 2 个单独的块替换您的语句,然后在错误情况下使用“提前退出”,则可以更优雅地完成:if/elseif/elseifreturn

function Read-Value {
    param (
        [string]$Subject,
        [string]$Name
    )
    if (-not ($config_json.PSObject.Properties.Name -contains $Subject)) {
        Write-Host "READ: No match for SUBJECT '$($Subject)'"
        return
    }

    # if we've reached this point it must mean the subject exists, assign to a variable
    $subjectObject = $config_json.PSObject.Properties[$Subject].Value

    if (-not ($subjectObject.PSObject.Properties.Name -contains $Name)) {
        Write-Host "READ: Matched Subject '$($Subject)' but not Name '$($Name)'"
        return
    }

    Write-Host "READ: Matched Subject '$($Subject)' and Name '$($Name):<$($result)>"
    $config_json.$Subject.$Name
}
1赞 mklement0 11/21/2023 #3

为了补充现有的有用答案

  • 使用可能更简单不完全等效的解决方案

    • PowerShell 通常允许使用变量引用等表达式作为属性名称,例如 $config_json.$Subject

    • 如果不需要区分属性是否存在以及属性是否存在但包含$null,则可以使用此功能来简化函数:

      function Read-Value {
        param (
          [string]$Subject,
          [string]$Name
        )
        Set-StrictMode -Version 1
        if ($null -eq ($subject = $config_json.$Subject)) {
          Write-Host "READ NO MATCH for SUBJECT " $Subject
        } elseif ($null -eq ($result = $subject.$Name)) {
          Write-Host "READ NO MATCH FOR SUBJECT " $Subject "NAME " $Name
        } else {
          Write-Host "READ MATCH FOR SUBJECT " $Subject "NAME " $Name "result <" $result ">"
          $value # Output
        }
      }
      
  • 并详细说明为什么$config_json。PSObject.Properties.$Subject 不起作用,不像 ,即使它尝试使用相同的技术:见下文。$config_json.PSObject.Properties[$Subject]


固有 psobject 属性的属性是任何对象的丰富反射源。.Properties

它包含 System.Management.Automation.PSPropertyInfo 实例的集合[1],每个实例都包含有关对象属性的信息,特别是 和 .
集合本身的类型为 System.Management.Automation.PSMemberInfoCollection<T>[2]
.Name.Value

由于 PowerShell 方便的成员访问枚举功能,你可以使用该集合的元素检索属性值;也就是说,此表达式等效于:$config_json.PSObject.Properties.Name.Name
$config_json.PSObject.Properties | ForEach-Object { $_.Name }

因此,不起作用,因为集合中的 -派生实例没有名为 的属性,例如。$config_json.PSObject.Properties.$SubjectPSPropertyInfoA

然而:

  • PSMemberInfoCollection<T> 通过其参数化 .Item 属性,允许通过其属性的值检索 -派生元素,该属性可以通过索引表示法访问;例如:PSPropertyInfo.Name

    # OK -> [PSNoteProperty] instance describing the .Bar property.
    # Append .Value to extract the value only.
    ([pscustomobject] @{ Foo=1; Bar=2 }).psobject.Properties['Bar']
    
  • 但是,由于它不是真正的字典,因此由于未实现 IDictionary 接口,因此不支持通常等效的点表示法

    # !! $null - dot notation does NOT work here: looks for a .Bar
    # !! property on every [PSNoteProperty] element, via member-access enumeration.
    ([pscustomobject] @{ Foo=1; Bar=2 }).psobject.Properties.Bar
    
    # By contrast, with a true dictionary such as [hashtable], index notation and
    # dot notation are largely interchangeable:
    # OK -> 2
    @{ Foo=1; Bar=2 }['Bar']
    # OK -> 2
    @{ Foo=1; Bar=2 }.Bar
    

[1] 从技术上讲,它们是从此抽象类派生的类型的实例,例如 System.Management.Automation.PSProperty,或者在本例中为 System.Management.Automation.PSNoteProperty

[2] 从技术上讲,它的类型为 [System.Management.Automation.PSMemberInfoIntegratingCollection<T>],这是一种派生自该类型的非公共类型。