4

Consider the following code:

$data = '[
    {
        "Name":  "banana",
        "Color":  "yellow"
    },
    {
        "Name":  "kiwi",
        "Color":  "green"
    },
    {
        "Name":  "apple",
        "Color":  "red"
    }
]'
# Returns 3 objects while only 1 was expected
$data | ConvertFrom-Json | Where-Object { $_.Name -eq 'banana' }

# Workaround, returns 1 object as expected:
($data | ConvertFrom-Json) | Where-Object { $_.Name -eq 'banana' }

Why is it not possible to use the first option? It seems like the Where-Object function does not correctly after an object is converted from json. This is happening on PowerShell version 5.1.

Are we missing something obvious here?

stackprotector
  • 10,498
  • 4
  • 35
  • 64
DarkLite1
  • 13,637
  • 40
  • 117
  • 214
  • It seems that some cmdlets have to be forced to be evaluated before used. `Get-Date` is one more such conundrum. – Dennis Sep 01 '21 at 07:16
  • This also produces an interesting result. `$data | ConvertFrom-Json | Where-Object Name -eq 'banana'` – Dennis Sep 01 '21 at 07:21
  • 2
    As of PowerShell v6, `convertfrom-json` does not enumerate JSON arrays before passing to the pipeline like other cmdlets but instead sends a single object down the pipeline. Using `()`, you are forcing enumeration. This is fixed in PowerShell v7 – AdminOfThings Sep 01 '21 at 08:58

1 Answers1

1

With:

$data | ConvertFrom-Json | Where-Object { $_.Name -eq 'banana' }

The following happens:

  1. ConvertFrom-Json returns an array of objects (which is an object itself). As this is the first (in the end also the only) "finished" object ConvertFrom-Json returns, it is passed as a whole down the pipeline. Remember, a cmdlet could return multiple arrays of objects in general.

  2. So, Where-Object receives only one object in this case (the whole array with three elements). $_ then references the whole array, not each element. Therefore, $_.Name does not return the name of one element, but a list of all element names. Furthermore, the term $_.Name -eq 'banana' in this case is not a boolean expression, but a filtered list of element names (the list then only contains 'banana'). As long as the list is not empty, it will be evaluated to $true by Where-Object and therefore your whole array (one object, with three elements) is piped further (printed in your case). So, it doesn't return three objects like you assumed, but one object, containing three objects.

Your other line in contrast:

($data | ConvertFrom-Json) | Where-Object { $_.Name -eq 'banana' }

Well, to make it short, does what you expect it to do. Why? Because the round brackets break the pipeline. Due to the brackets, everything inside the brackets will be completely evaluated, before something gets piped further. After your brackets are evaluated, there is an array, that will be piped further. A whole array will be piped element by element. So in this case, Where-Object receives three single objects, like you expected it.


Another nice example is this:

You cannot overwrite a file that your are currently reading:

Get-Content test.txt | Set-Content test.txt

But you can overwrite a file after you finished reading it:

(Get-Content test.txt) | Set-Content test.txt
stackprotector
  • 10,498
  • 4
  • 35
  • 64