为什么 ConvertFrom-Json 需要括号传递给 Foreach-Object?

Why are parentheses needed for ConvertFrom-Json piped to Foreach-Object?

我有这个函数可以获取 json 文件的内容。 我有一些(对我来说)意想不到的行为试图将其通过管道传递给 ConvertTo-JsonForeach-Object

Function GetConfigJson
{
    $ConfigPath = "pathtomyjsonfile.json"
    return Get-Content $ConfigPath | Out-String
}

json 的格式类似于 [{"key1":"value1"}, {"key2":"value2"}, ...]

为了测试我执行了以下操作的行为:

$a = 0;
GetConfigJson | ConvertFrom-Json | ForEach-Object { $a++ };
$b = 0;
ConvertFrom-Json GetConfigJson | ForEach-Object { $b++ };
$c = 0;
ConvertFrom-Json (GetConfigJson) | ForEach-Object { $c++ };
$d = 0;
(ConvertFrom-Json (GetConfigJson)) | ForEach-Object { $d++ };

Write-Host "Test1: $a | Test2: $b | Test3: $c | Test4: $d";

其中只有 Test4 打印预期的数字,Test1Test3 打印 1Test2 得到错误:ConvertFrom-Json : Invalid JSON primitive: GetConfigJson.

为什么我需要在 ConvertFrom-Json 周围加上括号才能将其实际作为对象数组进行管道传输?

(函数名称周围的括号 GetConfigJson 更容易接受 - 但我仍然想知道为什么我需要它?)

查看每个示例的输出类型可能会有所帮助 - 请参阅下面的细分。

辅助函数

我在下面的部分中使用了这两个辅助函数。注意 - 我猜你的配置在根部有一个数组,因为它似乎重现了这个问题,但如果这不是真的,请随时更新你的问题。

function GetConfigJson
{
    return "[{""name"":""first""}, {""name"":""second""}]"
}

function Write-Value
{
    param( $Value )
    write-host $Value.GetType().FullName
    write-host (ConvertTo-Json $Value -Compress)
}

然后使用您的示例:

示例 1

# example 1a - original example
PS> $a = 0
PS> GetConfigJson | ConvertFrom-Json | ForEach-Object { $a++ };
PS> $a
1

# example 1b - show return types and values
PS> GetConfigJson | ConvertFrom-Json | foreach-object { Write-Value $_ }
System.Object[]
{"value":[{"name":"first"},{"name":"second"}],"Count":2}

ConvertFrom-Json returns 一个有两个条目的数组对象,但是 Foreach-Object 只运行一次,因为它迭代单个数组 object,不是数组中的 2

示例 2

# example 2a - original example
PS> $b = 0;
PS> ConvertFrom-Json GetConfigJson | foreach-object { $b++ }
ConvertFrom-Json : Invalid JSON primitive: GetConfigJson.
At line:1 char:1
+ ConvertFrom-Json GetConfigJson | foreach-object { $b++ }
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [ConvertFrom-Json], ArgumentException
    + FullyQualifiedErrorId : System.ArgumentException,Microsoft.PowerShell.Commands.ConvertFromJsonCommand

# example 2b - show parameter types and values
PS> Write-Value GetConfigJson
System.String
"GetConfigJson"

ConvertFrom-Json 抛出异常,因为 PowerShell 将 GetConfigJson 视为文字字符串,但 "GetConfigJson" 显然无效 json,因此抛出异常。

示例 3

# example 3a - original example
PS> $c = 0;
PS> ConvertFrom-Json (GetConfigJson) | ForEach-Object { $c++ };
PS> $c
1

# example 3b - show parameter types and values
PS> ConvertFrom-Json (GetConfigJson) | ForEach-Object { Write-Value $_ };
System.Object[]
{"value":[{"name":"first"},{"name":"second"}],"Count":2}

这使用 GetConfigJson 周围的 Grouping Operator ( ... ),因此 PowerShell 将 GetConfigJson 计算为对函数的调用,而不是将其作为文字字符串。它首先执行 GetConfigJson 表达式,然后将其结果作为参数传递给 ConvertFrom-Json。但是,它仍然迭代单个数组对象而不是项目,因此 foreach-object 只运行一次。

示例 4

# example 4a - original example
PS> $d = 0;
PS> (ConvertFrom-Json (GetConfigJson)) | ForEach-Object { $d++ };
PS> $d
2

# example 4b - show parameter types and values
PS> (ConvertFrom-Json (GetConfigJson)) | ForEach-Object { Write-Value $_ };
System.Management.Automation.PSCustomObject
{"name":"first"}
System.Management.Automation.PSCustomObject
{"name":"second"}

我们在这里两次使用分组运算符 - 一次围绕 GetConfigJson 将其计算为表达式而不是字符串,一次围绕整个 ConvertFrom-Json (GetConfigJson)。外部 ( ... ) 导致 PowerShell“展开”单个数组对象并将其 发送到 Foreach-object 使用的管道中。这意味着 ForEach-Object 遍历 items 我们看到两个单独的值由``Write-Value```

写出

总结

你成功地解决了这个问题的许多 PowerShell 怪癖 - 希望这个答案有助于理解他们都在做什么以及为什么你会看到你所做的行为。

PowerShell 7 更新

根据@mklement0 的以下评论,ConvertFrom-Json 的行为从版本 7 开始发生变化 - 默认情况下它不枚举数组,需要 -NoEnumerate 选择退出。

例如,'[ 1, 2 ]' | ConvertFrom-Json | Measure-Object 现在在 v7+ 中报告 2,而需要 -NoEnumerate 才能获得 v6- 行为:'[ 1, 2 ]' | ConvertFrom-Json -NoEnumerate | Measure-Object(报告 1)。