Repository navigation
Description
Note: Perhaps the behavior in question will rarely be noticed as problematic in the real world, so perhaps the only thing that's needed is adding a note to the documentation - I'm unclear on whether this ultimately a non-issue.
alpha.17 fixed issue #3153 with respect to ConvertTo-Json no longer wrapping ConvertFrom-Json array output in an extraneous object with value and count properties.
However, ConvertFrom-Json still sends the objects resulting from converting a JSON array as a single array through the pipeline, which is a deviation from the usual cmdlet behavior of unwrapping collections and sending the items one by one.
> ('[ 1, 2 ]' | ConvertFrom-Json | Measure-Object).Count
1 # !! PS array `1, 2` was sent as a *single* object through the pipeline.Workaround: Enclose the ConvertFrom-Json part of the pipeline in (...), which forces enumeration:
> (('[ 1, 2 ]' | ConvertFrom-Json) | Measure-Object).Count
2 # OK, (...) forced enumerationAlternatively, simply insert a Write-Output pipeline segment:
> ('[ 1, 2 ]' | ConvertFrom-Json | Write-Output | Measure-Object).Count
2 # OK, Write-Output forced enumerationNote: On Windows PowerShell, as of v5.1, you paradoxically need Write-Output -NoEnumerate to prevent unwrapping from getting applied to the individual array elements as well.
On the plus side, as PetSerAl points out, this preserves the input structure in a round-trip in the case of single-element JSON arrays:
> '[ 1 ]' | ConvertFrom-Json | ConvertTo-Json
[
1
]It is only ConvertFrom-Json's send-the-array-as-a-whole approach that enables the above round trip.
By contrast, when you start with a PS array, it doesn't work that way (which illustrates the difference in pipeline behavior):
> , 1 | ConvertTo-Json
1 # !! Input PS array was unwrapped on sending it through the pipelineTo preserve the array, you must explicitly nest the array:
> , , 1 | ConvertTo-Json
[
1
]
# Alternative: explicitly prevent enumeration
> Write-Output -NoEnumerate (, 1) | ConvertTo-Json
[
1
]Environment data
PowerShell Core v6.0.0-alpha (v6.0.0-alpha.17) on Darwin Kernel Version 16.4.0: Thu Dec 22 22:53:21 PST 2016; root:xnu-3789.41.3~3/RELEASE_X86_64
Activity
BrucePay commentedon Mar 31, 2017
An alternate illustration of this behavior is
'[ 1, 2 ]' | ConvertFrom-Json | foreach { $_.Count }which returns the value 2 indicating that the array is passed as a single value as opposed to'[ 1, 2 ]' | ConvertFrom-Json | write-output | foreach { $_.Count }which will return 1,1.mklement0 commentedon Mar 31, 2017
Thanks, Bruce Payette (@BrucePay), but what I'm unclear on is: is this behavior - given that it deviates from usual collection-unwrapping behavior - problematic in practice?
mklement0 commentedon Jun 10, 2017
The more I think about it, the more I believe this behavior should be changed:
I just came across a real-world problem in which the behavior is problematic, and I found that despite being aware of the issue in principle, I simply forgot, which stumped me for a while.
Having to remember this exception is a burden, especially given that there's no obvious reason for it and that the only benefit is the ability to round-trip a single-item array - something that it isn't even expected to work with PowerShell's own arrays.
SteveL-MSFT commentedon Jun 14, 2017
@PowerShell/powershell-committee reviewed this and agrees that we should be consistent with expected PowerShell semantics and a single element array would be a single item. However, we also don't want to revert the original problem in #3153
bojanrajkovic commentedon Nov 17, 2017
This behavior needs to be fixed--it means you can't effectively compose pipelines that involve JSON arrays, unless you either assign to an intermediate variable, or wrap in a "subshell" to terminate the pipeline.
That is by no means obvious to discover--I found it before I found this issue by pure accident, mostly because I suspected something wrong with the pipeline objects and wanted to call
GetTypeon the output ofConvertFrom-Json. Another engineer had already spent at least 20 minutes wondering why his pipeline didn't work.This looks really bad as a user--Powershell's object-oriented nature should make pipelines like this a breeze, instead you get hard-to-diagnose Heisenbugs (this would have disappeared as soon as someone tried assigning it to a variable, only to reappear again later).
san7hos commentedon Nov 24, 2017
I think the way to fix the issue could be by introducing a switch to the ConvertFrom-Json that would modify the behaviour, either returning an array or pipeline objects, e.g.:
I wrote an advanced function while trying to solve the issue
However, when reading throughout this ticket, I realized that
Write-Outputcould be used instead.mklement0 commentedon Nov 24, 2017
I've added a simple workaround to the original post: in short: enclosing the
Convert-FromJsonpart of the pipeline in(...)forces enumeration; simply inserting aWrite-Outputpipeline segment would work too (except in Windows PowerShell, where you'd paradoxically needWrite-Output -NoEnumerate)).I like the idea of introducing the switch, but I'd reverse the logic: pipeline-friendliness should be the default; thus, something like
-NoEnumerate- in line withWrite-Output's parameter - would be more appropriate.Obviously, however, that would be a breaking change.
elijahgagne commentedon Dec 2, 2017
I'm glad I found this post. I spent a couple hours trying to figure out what was wrong. I originally thought the API I was working with was doing something improper. I switched over to Python and realized that the API was returning good results, the problem was Convert-FromJson was not working the way I expected it to.
AikenBM commentedon Mar 6, 2018
I came across this in a StackOverflow question where the user was trying to pipeline the output of
ConvertFrom-Json. The difference in these behaviors is very much unexpected:Once you know that
ConvertFrom-Jsonis wrapping the item in an array it's clear what's going on, but until then it's very confusing.ConvertFrom-Csvcertainly doesn't behave like this, but, of course, JSON is much more structured than CSV.mklement0 commentedon Mar 6, 2018
AikenBM
Yes, but the problem is remembering it - given that it works differently from the rest of PowerShell; the benefits of this deviation (round-tripping of single-item JSON arrays) are too slim to warrant it, in my opinion.
48 remaining items
danstur commentedon Nov 2, 2019
Steve Lee (@SteveL-MSFT) I have to agree with Michael Klement (@mklement0) here, I'm also disappointed. I picked this issue not just because it's a nice entry level problem to look at the code base but also because not only have I fallen into this trap, but I also had to help lots of colleagues with the same issue.
Not only is this behavior completely unintuitive, since it behaves contrary to almost (? I can't think of a single counterexample actually) everything else, it is also as far as I can see nowhere documented.
And not only is the behavior unintuitive, when trying to analyse an issue (which will often involve assigning partial expressions to variables to make it easier to debug), you're likely to unknowingly fix the underlying problem which means you might not even understand the problem at all, just that the problem magically went away after a bit of debugging.
The same problem also makes the current solution very fragile, since a simple refactoring will change the observable behavior.
vexx32 commentedon Nov 2, 2019
To add one further point to what's already mentioned, I think we should avoid the inherent inconsistency of having one common command with
-NoEnumerate(i.e., Write-Output) and other with-EnumerateVariant behaviours may be desired, and that is why we have the option to select non-standard behaviour, but we should not have two completely different standard behaviours. Documentation or no, this difference is not an immediately obvious behaviour in that users not already very familiar with the commands will be able to diagnose. As such, I think the default behaviours should be consistent.
SteveL-MSFT commentedon Nov 3, 2019
Daniel Sturm (@danstur) I believe the @PowerShell/powershell-committee misunderstood the situation when we discussed it, we'll discuss this again on Monday.
SteveL-MSFT commentedon Nov 7, 2019
@PowerShell/powershell-committee reviewed this again. There is a case of an array of arrays that would break with this change, but this is unlikely and can be addressed with the
-noenumerateswitch. So we support adding the-noenumerateswitch to return an array as a single object and haveConvertFrom-Jsonreturn elements individually to the pipeline following expected PowerShell behavior by default for arrays.ConvertFrom-Json: Unwrap Collections by default
PMC Tools: Work around PowerShell/PowerShell#3424
PMC Tools: Work around PowerShell/PowerShell#3424 (#22134)
iSazonov commentedon Sep 9, 2020
After #10861 can we close the issue?
mklement0 commentedon Sep 9, 2020
Ilya (@iSazonov): Yes, we can: