Skip to content

ConvertFrom-Json sends objects converted from a JSON array as an *array* through the pipeline. #3424

Closed

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 enumeration

Alternatively, simply insert a Write-Output pipeline segment:

> ('[ 1, 2 ]' | ConvertFrom-Json | Write-Output | Measure-Object).Count
2  # OK, Write-Output forced enumeration

Note: 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 pipeline

To 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

  1. BrucePay commented on Mar 31, 2017

    @BrucePay
    Collaborator

    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.

  2. mklement0 commented on Mar 31, 2017

    @mklement0
    ContributorAuthor

    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?

  3. mklement0 commented on Jun 10, 2017

    @mklement0
    ContributorAuthor

    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.

  4. added
    Review - CommitteeThe PR/Issue needs a review from the PowerShell Committee
    Committee-ReviewedPS-Committee has reviewed this and made a decision
    and removed
    Review - CommitteeThe PR/Issue needs a review from the PowerShell Committee
    on Jun 10, 2017
  5. SteveL-MSFT commented on Jun 14, 2017

    @SteveL-MSFT
    Member

    @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

  6. bojanrajkovic commented on Nov 17, 2017

    @bojanrajkovic

    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 GetType on the output of ConvertFrom-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).

  7. san7hos commented on Nov 24, 2017

    @san7hos

    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.:

    ConvertFrom-Json -PipelineFriendly

    I wrote an advanced function while trying to solve the issue

    function Split-Array {
        [CmdletBinding()]
        param (
            [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
            [System.Object[]] $Array
        )
        
        process {
            foreach ($item in $Array) {
                $item
            }
        }
    }

    However, when reading throughout this ticket, I realized that Write-Output could be used instead.

  8. mklement0 commented on Nov 24, 2017

    @mklement0
    ContributorAuthor

    I've added a simple workaround to the original post: in short: enclosing the Convert-FromJson part of the pipeline in (...) forces enumeration; simply inserting a Write-Output pipeline segment would work too (except in Windows PowerShell, where you'd paradoxically need Write-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 with Write-Output's parameter - would be more appropriate.

    Obviously, however, that would be a breaking change.

  9. elijahgagne commented on Dec 2, 2017

    @elijahgagne

    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.

  10. AikenBM commented on Mar 6, 2018

    @AikenBM

    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:

    PS> ConvertFrom-Json '[1, 2, 3]' | ForEach-Object  {": $_"}
    : 1 2 3
    
    PS> $x = ConvertFrom-Json '[1, 2, 3]'
    PS> $x | ForEach-Object  {": $_"}
    : 1
    : 2
    : 3
    PS> ,$x | ForEach-Object  {": $_"}
    : 1 2 3
    
    PS> (ConvertFrom-Json '[1, 2, 3]') | ForEach-Object  {": $_"}
    : 1
    : 2
    : 3
    

    Once you know that ConvertFrom-Json is wrapping the item in an array it's clear what's going on, but until then it's very confusing. ConvertFrom-Csv certainly doesn't behave like this, but, of course, JSON is much more structured than CSV.

  11. mklement0 commented on Mar 6, 2018

    @mklement0
    ContributorAuthor

    AikenBM

    Once you know that ConvertFrom-Json is wrapping the item in an array

    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.

  12. 48 remaining items

  13. danstur commented on Nov 2, 2019

    @danstur
    Contributor

    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.

  14. vexx32 commented on Nov 2, 2019

    @vexx32
    Collaborator

    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 -Enumerate

    Variant 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.

  15. SteveL-MSFT commented on Nov 3, 2019

    @SteveL-MSFT
    Member

    Daniel Sturm (@danstur) I believe the @PowerShell/powershell-committee misunderstood the situation when we discussed it, we'll discuss this again on Monday.

  16. added
    Committee-ReviewedPS-Committee has reviewed this and made a decision
    and removed
    Review - CommitteeThe PR/Issue needs a review from the PowerShell Committee
    on Nov 6, 2019
  17. SteveL-MSFT commented on Nov 7, 2019

    @SteveL-MSFT
    Member

    @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 -noenumerate switch. So we support adding the -noenumerate switch to return an array as a single object and have ConvertFrom-Json return elements individually to the pipeline following expected PowerShell behavior by default for arrays.

  18. iSazonov commented on Sep 9, 2020

    @iSazonov
    Collaborator

    After #10861 can we close the issue?

  19. mklement0 commented on Sep 9, 2020

    @mklement0
    ContributorAuthor

    Ilya (@iSazonov): Yes, we can:

    PS> ('[ 1, 2, 3]' | ConvertFrom-Json | Measure-Object).Count
    3 # implies that the array elements were sent one by one through the pipeline.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Committee-ReviewedPS-Committee has reviewed this and made a decisionResolution-FixedThe issue is fixed.WG-Cmdletsgeneral cmdlet issues

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions