This JSON array converted to powershell object seems to be somehow get handled as single string when streamed to ForEach-Object.
"[1, 2, 3]" | ConvertFrom-Json | ForEach-Object {Write-Host "Number: $_"}
Prints:
Number: 1 2 3
Expecting:
Number: 1
Number: 2
Number: 3
Although: "[1, 2, 3]" | ConvertFrom-Json
prints:
1
2
3
I was of course trying to do something bit more complex with Azure CLI, but this was the problem that wasted me quite some time.
And actually setting parenthesis like this seems to work as I expect:
("[1, 2, 3]" | ConvertFrom-Json) | ForEach-Object {Write-Host "Number: $_"}
And outputs:
Number: 1
Number: 2
Number: 3
Is this a piping issue in powershell or do I just not understand piping in powershell.
My powershell version is as follows:
> $PSversionTable
Name Value
---- -----
PSVersion 5.1.22000.282
PSEdition Desktop
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...}
BuildVersion 10.0.22000.282
CLRVersion 4.0.30319.42000
WSManStackVersion 3.0
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
CodePudding user response:
This is by design, the Windows PowerShell version of ConvertFrom-Json
explicitly requests the runtime doesn't immediately enumerate array output (Write-Output $array -NoEnumerate
basically).
The reason is that a JSON document with a top-level single-item array like [1]
would be indistinguishable from the document 1
if PowerShell's default pipeline enumeration behavior was allowed.
How to work around this behavior?
As you've found, you can work around this by nesting the pipeline that ends with ConvertFrom-Json
, using either the grouping operator (...)
, or either of the subexpression operators $(...)
or @(...)
:
("[1, 2, 3]" | ConvertFrom-Json) | ForEach-Object {Write-Host "Number: $_"}
# or
@("[1, 2, 3]" | ConvertFrom-Json) | ForEach-Object {Write-Host "Number: $_"}
# or
$("[1, 2, 3]" | ConvertFrom-Json) | ForEach-Object {Write-Host "Number: $_"}
You can also insert another command into the pipeline - the request to not enumerate array output only affects the immediate downstream command, not the rest of the pipeline:
"[1, 2, 3]" | ConvertFrom-Json | ForEach-Object { $_ } | ForEach-Object {Write-Host "Number: $_"}
# ^ ^
# This one will not enumerate output |
# But this one will
Or, if possible, upgrade to the latest version of PowerShell. ConvertFrom-Json
in PowerShell 7.x has been updated so that it:
- no longer requests no output enumeration by default
- allows the user to control the behavior with an
-NoEnumerate
parameter:
# This will work as you expected in PowerShell 7
"[1, 2, 3]" | ConvertFrom-Json | ForEach-Object {Write-Host "Number: $_"}
# This will work the same as Windows PowerShell in PowerShell 7
"[1, 2, 3]" | ConvertFrom-Json -NoEnumerate | ForEach-Object {Write-Host "Number: $_"}