3

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

1 Answer 1

8
+500

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: $_"}
Sign up to request clarification or add additional context in comments.

5 Comments

That was a quick answer and in amazing detail! Thank you. Now contemplating if I dare installing powershell 7 as I am not sure how I installed the initial version. I also find my self struggling all the time with powershell output and what format/type different commands are outputting.
@user232548 You're welcome! Windows PowerShell has been included in the operating system installation since Windows 7/2008R2, so it's likely that you didn't actually install it yourself. FWIW I'd love to help you gain a better understanding of why formatting works the way it works, but you're gonna have to ask more precisely :)
What is the best way to check the type of a variable or command output? And especially in this case is there any way I could have debugged my self to a solution? I actually found the solution thinking I would attach debugger, so I split the command into two parts, but when writing ConvertFrom-Json output to variable would seem to already do this enumeration, so even attaching debugger would not seem to give me any hint of what is going on.
Good question. It's a bit tricky to diagnose actually - I answered above based on experience, but my personal approach to troubleshooting this would start with Trace-Command: Trace-Command -Expression { "[1, 2]" |ConvertFrom-Json |ForEach-Object { "$_" }} -Name ParameterBind* -PSHost <- trace ouput would show the type of the input object pushed to ForEach-Object listed as [System.Object[]] rather than [int] or [int64].
Yup, that trace-command seems very useful. It even seems to print the arguments bound at each step. So if I used one of your suggested workarounds I can see first ForEach-Object to bind value "1 2" to input and the second one bind "1" and "2" separately. And as you mentioned first one would show type [System.Object[]] and second one `[int]. This is what I have been missing for a long time.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.