arraysjsonpowershellenumeration

Read multiple JSON files into an array of Powershell objects and filter out those with the same value for a property


It's my first time, so let me know if I'm doing something wrong with the layout of my question.

I have a lot of JSON files with filenames that follow a naming convention, i.e. file1.json, file2.json, etc. Each of which are likely to have multiple objects which look like:

[
    {
        "Forename":  "Jim",
        "Surname":  "Cook",
        "Gender":  "M",
        "DOB":  "12-03-1994"
    },
    {
        "Forename":  "Sarah",
        "Surname":  "Parker",
        "Gender":  "F",
        "DOB":  "01-02-1983"
    },
    {
        "Forename":  "Alan",
        "Surname":  "Flemming",
        "Gender":  "M",
        "DOB":  "27-10-1989"
    }
]

In Powershell, I would like to convert these JSON objects into Powershell objects and then select objects with the same value for a property, like people whose first name is "Jim".

So far I've achieved this:

@(Get-ChildItem "file*.json" | %{Get-Content $_.FullName | Out-String | ConvertFrom-Json}) | Where-Object {$_.Forename -eq "Jim"}

This works when there is only one file to work with, which outputs:

Forename Surname Gender DOB
-------- ------- ------ ---
Jim      Cook    M      12-03-1994

However it fails and outputs all objects when used with multiple files, as though the Where-Object is being ignored. The result can look like this:

Forename Surname  Gender DOB
-------- -------  ------ ---
Jim      Cook     M      12-03-1994
Sarah    Parker   F      01-02-1983
Alan     Flemming M      27-10-1989
Bill     Preston  M      04-07-1975
Helen    Smith    F      03-12-2001

Can someone please suggest what I'm doing wrong here and how it can be fixed to get the correct result? Thanks


Solution

  • The problem is that, in Windows PowerShell (the legacy, ships-with-Windows edition of PowerShell whose latest and last version is 5.1), ConvertFrom-Json outputs (converted-from-)JSON arrays as single objects rather than element by element, as is otherwise typical in PowerShell.

    This results in the entire array getting output by Where-Object if (at least) one of its elements has a .ForeName property with value Jim, thanks to member-access enumeration.

    The workaround is to force enumeration, which in the simplest case is achieved by wrapping the ConvertFrom-Json call in (...):

    Get-ChildItem file*.json | ForEach-Object {
      (Get-Content -Raw $_.FullName | ConvertFrom-Json)
    } | Where-Object { $_.Forename -eq "Jim" }
    

    Note that I've replaced Get-Content $_.FullName | Out-String with Get-Content -Raw $_.FullName (PSv3+), which is both more concise and more efficient for retrieving a file's content as a single, multi-line string.