Home > database >  PowerShell - How do I iterate a PSCustomObject nested object?
PowerShell - How do I iterate a PSCustomObject nested object?

Time:04-03

I feel like this is something simple and I'm just not getting it, and I'm not sure if my explanation is great. I have this below JSON file, and I want to get "each App" (App1, App2, App3) under the "New" object

In this script line below I'm essentially trying to replace "TestApp2" with some variable. I guess I'm trying to get TestApp2 as an object without knowing the name. And I realize that the foreach loop doesn't do anything right now

Write-Host $object.Value.TestApp2.reply_urls

JSON:

{
  "New": {
    "App1": {
      "reply_urls": [
        "https://testapp1url1"
      ]
    },
    "App2": {
      "reply_urls": [
        "https://testapp2url1",
        "https://testapp2url2"
      ]
    },
    "App3": {
      "reply_urls": [
        "https://testapp3url1",
        "https://testapp3url2",
        "https://testapp3url3"
      ]
    }
  },
  "Remove": {
      "object_id": [
        ""
      ]
  }
}

Script:

$inputFile = Get-Content -Path $inputFilePath -Raw | ConvertFrom-Json
foreach ($object in $inputFile.PsObject.Properties)
{
    switch ($object.Name)
    {
        New
        {
            foreach ($app in $object.Value)
            {
               Write-Host $object.Value.TestApp2.reply_urls
               # essentially want to replace this line with something like
               # Write-Host $app.reply_urls
            }
        }

        Remove
        {
        }
    }
}

Output:

https://testapp2url1 https://testapp2url2

CodePudding user response:

You can access the object's PSObject.Properties to get the property Names and property Values, which you can use to iterate over.

For example:

foreach($obj in $json.New.PSObject.Properties) {
    $out = [ordered]@{ App = $obj.Name }
    foreach($url in $obj.Value.PSObject.Properties) {
        $out[$url.Name] = $url.Value
    }
    [pscustomobject] $out
}

Produces the following output:

App  reply_urls
---  ----------
App1 {https://testapp1url1}
App2 {https://testapp2url1, https://testapp2url2}
App3 {https://testapp3url1, https://testapp3url2, https://testapp3url3}

If you just want to output the URL you can skip the construction of the PSCustomObject:

foreach($obj in $json.New.PSObject.Properties) {
    foreach($url in $obj.Value.PSObject.Properties) {
        $url.Value
    }
}

CodePudding user response:

Complementing Santiago Squarzon's helpful answer, I've looked for a generalized approach to get JSON properties without knowing the names of their parents in advance.

I wrote a little helper function Expand-JsonProperties that "flattens" the JSON. This allows us to use simple non-recursive Where-Object queries for finding properties, regardless how deeply nested they are.

Function Expand-JsonProperties {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory, ValueFromPipeline)] [PSCustomObject] $Json,
        [Parameter()] [string] $Path,
        [Parameter()] [string] $Separator = '/'
    )
    
    process {
        $Json.PSObject.Properties.ForEach{

            $propertyPath = if( $Path ) { "$Path$Separator$($_.Name)" } else { $_.Name }

            if( $_.Value -is [PSCustomObject] ) {
                Expand-JsonProperties $_.Value $propertyPath
            }
            else {
                [PSCustomObject]@{
                    Path   = $propertyPath
                    Value  = $_.Value
                }
            }
        }
    }
}

Given your XML sample we can now write:

$inputFile = Get-Content -Path $inputFilePath -Raw | ConvertFrom-Json

$inputFile | Expand-JsonProperties | Where-Object Path -like '*/reply_urls'

Output:

Path                Value
----                -----
New/App1/reply_urls {https://testapp1url1}
New/App2/reply_urls {https://testapp2url1, https://testapp2url2}
New/App3/reply_urls {https://testapp3url1, https://testapp3url2, https://testapp3url3}

Bonus code:

I've implemented a few alternative ways of flattening JSON (or any other nested [PSObject] structures) in this Gist. They are implemented recursion-free, so they should be somewhat faster (as the parameter-binding overhead of the recursion is removed).

  • Related