Home > Enterprise >  Why does powershell ConvertFrom-Json not work when converting json array and streaming output to For
Why does powershell ConvertFrom-Json not work when converting json array and streaming output to For

Time:02-18

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: $_"}
  • Related