如果我在PowerShell中运行此程序,则希望看到输出0
(零):
Set-StrictMode -Version Latest
$x = "[]" | ConvertFrom-Json | Where { $_.name -eq "Baz" }
Write-Host $x.Count
相反,我收到此错误:
The property 'name' cannot be found on this object. Verify that the property exists and can be set.
At line:1 char:44
+ $x = "[]" | ConvertFrom-Json | Where { $_.name -eq "Baz" }
+ ~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : PropertyAssignmentException
如果我在"[]" | ConvertFrom-Json
上加上括号,它将变为:
$y = ("[]" | ConvertFrom-Json) | Where { $_.name -eq "Baz" }
Write-Host $y.Count
然后它“起作用”。
在引入括号之前出了什么问题?
要解释有关“作品”的引号-设置严格模式Set-StrictMode -Version Latest
表示我在.Count
对象上调用$null
。这可以通过包裹在@()
中来解决:
$z = @(("[]" | ConvertFrom-Json) | Where { $_.name -eq "Baz" })
Write-Host $z.Count
我觉得这很不满意,但这是实际问题的一部分。
答案 0 :(得分:4)
PowerShell为什么将
Where
的谓词应用于空列表?
因为ConvertFrom-Json
告诉Where-Object
不要尝试枚举其输出。
因此,PowerShell尝试访问空数组本身上的name
属性,就像我们要做的那样:
$emptyArray = New-Object object[] 0
$emptyArray.name
将ConvertFrom-Json
括在括号中时,powershell会将其解释为执行并在之前结束的单独管道。任何输出都可以发送到{{1} },因此Where-Object
不知道Where-Object
希望它像这样对待数组。
我们可以在Powershell中通过使用ConvertFrom-Json
开关参数集显式调用Write-Output
来重新创建此行为:
-NoEnumerate
# create a function that outputs an empty array with -NoEnumerate
function Convert-Stuff
{
Write-Output @() -NoEnumerate
}
# Invoke with `Where-Object` as the downstream cmdlet in its pipeline
Convert-Stuff | Where-Object {
# this fails
$_.nonexistingproperty = 'fail'
}
# Invoke in separate pipeline, pass result to `Where-Object` subsequently
$stuff = Convert-Stuff
$stuff | Where-Object {
# nothing happens
$_.nonexistingproperty = 'meh'
}
内部调用Write-Output -NoEnumerate
,这反过来导致运行时 not 枚举与下游cmdlet进行参数绑定期间的Cmdlet.WriteObject(arg, false)
值(在您的情况下) arg
)
为什么这是可取的?
在解析JSON的特定上下文中,此行为确实可能是理想的:
Where-Object
既然我已经向其传递了5个有效的JSON文档,那么我不应该期望$data = '[]', '[]', '[]', '[]' |ConvertFrom-Json
中有5个对象吗? :-)
答案 1 :(得分:2)
使用空数组作为直接管道输入,什么都不会通过管道发送,因为数组是枚举的 ,并且由于没有什么可枚举的-空数组没有元素-不会执行Where
脚本块:
# The empty array is enumerated, and since there's nothing to enumerate,
# the Where[-Object] script block is never invoked.
@() | Where { $_.name -eq "Baz" }
相反,"[]" | ConvertFrom-Json
生成一个空数组作为单个输出对象,而不是枚举其(不存在的)元素,因为 ConvertFrom-Json
by设计不会枚举其输出的数组的元素;等效于:
# Empty array is sent as a single object through the pipeline.
# The Where script block is invoked once and sees $_ as that empty array.
Write-Output -NoEnumerate @() | Where { $_.name -eq "Baz" }
ConvertFrom-Json
在PowerShell上下文中的行为令人惊讶-cmdlet通常枚举多个输出-但在JSON上下文中有意义解析;毕竟,如果ConvertFrom-Json
枚举空数组,则信息将丢失,因为您将无法将其与空JSON输入({ {1}}。
这种紧张关系在this GitHub issue中进行了讨论。
共识是,两个用例都是合法的,并且用户应通过开关在两个行为之间(枚举与否)选择选择 ;从PowerShell Core 6.2.0开始,尚未做出正式决定,但是如果要保留向后兼容性,则必须是 opt-in 的枚举行为(例如,{{1 }}。
如果需要枚举,则目前的-晦涩-解决方法是通过将"" | ConvertFrom-Json
调用简单地包含在-Enumerate
中来强制枚举(将其转换为 expression ,并且表达式在管道中使用时始终会枚举命令的输出):
ConvertFrom-Json
关于您尝试过的事情:您尝试访问(...)
属性和使用# (...) around the ConvertFrom-Json call forces enumeration of its output.
# The empty array has nothing to enumerate, so the Where script block is never invoked.
("[]" | ConvertFrom-Json) | Where { $_.name -eq "Baz" }
的情况:
.Count
在@(...)
中包装了$y = ("[]" | ConvertFrom-Json) | Where { $_.name -eq "Baz" }
$y.Count # Fails with Set-StrictMode -Version 2 or higher
调用之后,您的总体命令将返回“ nothing”:松散地说,ConvertFrom-Json
,但更准确地说,是一个“数组值null”,它是(...)
单例,表示命令没有输出。 (在大多数情况下,后者与$null
的处理方式相同,但在用作管道输入时尤其明显。)
[System.Management.Automation.Internal.AutomationNull]::Value
没有$null
属性,这就是为什么[System.Management.Automation.Internal.AutomationNull]::Value
或更高版本会导致.Count
错误的原因。
通过将整个管道包装在数组子表达式运算符Set-StrictMode -Version 2
中,可以确保将输出视为 array ,并与[array-valued null输出一起创建一个空数组-确实具有The property 'count' cannot be found on this object.
属性。
请注意,假设PowerShell添加了一个{{},您您应该在@(...)
和.Count
上呼叫.Count
1}}属性(每个)(如果尚不存在)(包括标量),以表扬一下,以统一对集合和标量的处理。
也就是说,将$null
设置为[System.Management.Automation.Internal.AutomationNull]::Value
(默认设置)或.Count
时,以下起作用,并且-明智地-返回{{1} }:
Set-StrictMode
上述当前 不适用于-Off
或更高版本(自PowerShell Core 6.2.0起),应被视为 bug ,如this GitHub issue中所述(杰弗里·斯诺弗(Jeffrey Snover),不少)。