5

How do I iterate through JSON array which is converted to PSCustomObject with ConvertFrom-JSON? Using foreach does not work.

$jsonArray ='[{"privateKeyLocation" : "C:\\ProgramData\\docker\\certs.d\\key.pem"},
{"publicKeyLocation" : "C:\\ProgramData\\docker\\certs.d\\cert.pem"},
{"publicKeyCALocation" : "C:\\ProgramData\\docker\\certs.d\\ca.pem"}]'
$json = convertfrom-json $jsonArray
$json | foreach {$_}

Returns

privateKeyLocation
------------------
C:\ProgramData\docker\certs.d\key.pem

Enumerator though says there are 3 members of array

>$json.Count
3

5 Answers 5

2

The problem that you are having is not specific to it being a JSON array, it has to do with how custom objects in an array are displayed by default. The simplest answer is to pipe it to Format-List (or FL for short).

PS C:\Users\TMTech> $JSON|FL


privateKeyLocation : C:\ProgramData\docker\certs.d\key.pem

publicKeyLocation : C:\ProgramData\docker\certs.d\cert.pem

publicKeyCALocation : C:\ProgramData\docker\certs.d\ca.pem

Aside from that, when PowerShell outputs an array of objects it bases the columns that it displays upon the properties of the first object in the array. In your case that object has one property named 'privateKeyLocation', so that is the only column that appears, and since the other two objects do not have that property it does not display anything for them. If you want to keep it as a table you could gather all potential properties, and add them to the first item with null values, and that would allow you to display it as a table, but it still wouldn't look very good:

$json|%{$_.psobject.properties.name}|select -Unique|?{$_ -notin $json[0].psobject.Properties.Name}|%{Add-Member -InputObject $JSON[0] -NotePropertyName $_ -NotePropertyValue $null}

Then you can output as a table and get everything:

PS C:\Users\TMTech> $json

privateKeyLocation                    publicKeyLocation                      publicKeyCALocation                 
------------------                    -----------------                      -------------------                 
C:\ProgramData\docker\certs.d\key.pem                                                                            
                                      C:\ProgramData\docker\certs.d\cert.pem                                     
                                                                             C:\ProgramData\docker\certs.d\ca.pem

Edit: To get the value of each object in this case is tricky, because the property that you want to expand keeps changing for each object. There's two ways to do this that I can think of, what I would consider the right way, and then there's the easy way. The right way to do it would be to determine the property that you want to expand, and then reference that property directly:

$JSON |%{
    $PropName = $_.PSObject.Properties.Name
    $_.$PropName
}

That'll do what you want, but I think easier would be to pipe to Format-List, then Out-String, wrap the whole thing in parenthesis, split on new lines and replace everything up to : which should just leave you with the paths you want.

($JSON|FL|Out-String) -split '[\r\n]+' -replace '(?m)^.+ : '|?{$_}
Sign up to request clarification or add additional context in comments.

5 Comments

I need to get second property of each link (the one which is file system path). How do I iterate and get that information?
Also why foreach does not work in my example if it's an array? Why only first object is returned?
The reason that ForEach does not work is that it passes the data down the pipeline, so all you're doing is passing the object down the pipe, when it reaches the end it then gets determined how to format it, which by default is as an array of objects, and past that you can refer to my answer as to why it fails there. Same reason that Write-Host works while Write-Output does not. Write-Output passes the object down the pipeline.
So is there an easy way to get second item for each object somehow?
There is no second item for each object. Each object has 1 property. What you want is the value of that property. My updated answer gives two ways to accomplish that.
2

Most simple way, should be like this

    $ret ='[your json]'
    $ret | ConvertFrom-Json
    $data = $ret | ConvertFrom-Json

    foreach($data in $ret | ConvertFrom-Json) {
        Write-Host $data;
    }

Comments

1

Interesting enough. I responded to this exact question from the same OP on another forum. Though my response was just RegEx and be done with it, with no additional conversion.

Of course there are several ways to do this. The below is just what I came up with.
$jsonArray = '[{"privateKeyLocation" : "C:\\ProgramData\\docker\\certs.d\\key.pem"},
{"publicKeyLocation" : "C:\\ProgramData\\docker\\certs.d\\cert.pem"},
{"publicKeyCALocation" : "C:\\ProgramData\\docker\\certs.d\\ca.pem"}]'

([regex]::Matches($jsonArray,'(?<=\").:\\[^\"]+(?=\")').Value) -replace '\\\\','\' `
| ForEach { 
            If (Test-Path -Path $_)
            {"path $_ found"}
            Else {Write-Warning "Path $_ not found"}
            }


WARNING: Path C:\ProgramData\docker\certs.d\key.pem not found
WARNING: Path C:\ProgramData\docker\certs.d\cert.pem not found
WARNING: Path C:\ProgramData\docker\certs.d\ca.pem not found

So, maybe not as elegant as what was posted here, but it would get the OP where they wanted to be.

So, consolidating everything TheMadTechnician gave and what the OP is after, and attempting to make it as concise as possible, would give the OP the below (I added a element to show a positive response):

Clear-Host 
($jsonArray = @'
[{"privateKeyLocation" : "C:\\ProgramData\\docker\\certs.d\\key.pem"},
{"publicKeyLocation" : "C:\\ProgramData\\docker\\certs.d\\cert.pem"},
{"publicKeyCALocation" : "C:\\ProgramData\\docker\\certs.d\\ca.pem"},
{"publicKeyTestFileLocation" : "D:\\Temp\\test.txt"}]
'@ | ConvertFrom-Json | Format-List | Out-String) -split '[\r\n]+' -replace '(?m)^.+ : '`
| Where-Object {$_} | ForEach {
    If(Test-Path -Path $_){"The path $_ was found"}
    Else{Write-Warning -Message "The path $_ was not found}"}
}

WARNING: The path C:\ProgramData\docker\certs.d\key.pem was not found}
WARNING: The path C:\ProgramData\docker\certs.d\cert.pem was not found}
WARNING: The path C:\ProgramData\docker\certs.d\ca.pem was not found}
The path D:\Temp\test.txt was found

Which one is more to his liking is a matter of the OP choice of course.

The performance between the two varied on each test run, but the fastest time using the straight RegEx approach was:

Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 43
Ticks             : 439652
TotalDays         : 5.08856481481481E-07
TotalHours        : 1.22125555555556E-05
TotalMinutes      : 0.000732753333333333
TotalSeconds      : 0.0439652
TotalMilliseconds : 43.9652

and the fastest on the consolidated version here was:

Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 54
Ticks             : 547810
TotalDays         : 6.34039351851852E-07
TotalHours        : 1.52169444444444E-05
TotalMinutes      : 0.000913016666666667
TotalSeconds      : 0.054781
TotalMilliseconds : 54.781

Updating to add iRon's take on this topic

So this...

$jsonArray ='[{"privateKeyLocation" : "C:\\ProgramData\\docker\\certs.d\\key.pem"},
{"publicKeyLocation" : "C:\\ProgramData\\docker\\certs.d\\cert.pem"},
{"publicKeyCALocation" : "C:\\ProgramData\\docker\\certs.d\\ca.pem"}]'
$json = convertfrom-json $jsonArray

$json | ForEach {
    $Key = $_.psobject.properties.name; 
    "Testing for key " + $_.$Key
    Test-Path -Path $_.$Key
} 


Testing for key C:\ProgramData\docker\certs.d\key.pem
False
Testing for key C:\ProgramData\docker\certs.d\cert.pem
False
Testing for key C:\ProgramData\docker\certs.d\ca.pem
False

... and this:

('[{"privateKeyLocation" : "C:\\ProgramData\\docker\\certs.d\\key.pem"},
{"publicKeyLocation" : "C:\\ProgramData\\docker\\certs.d\\cert.pem"},
{"publicKeyCALocation" : "C:\\ProgramData\\docker\\certs.d\\ca.pem"}]' `
| convertfrom-json) | ForEach {
    $Key = $_.psobject.properties.name; 
    "Testing for key " + $_.$Key
    Test-Path -Path $_.$Key
} 

Testing for key C:\ProgramData\docker\certs.d\key.pem
False
Testing for key C:\ProgramData\docker\certs.d\cert.pem
False
Testing for key C:\ProgramData\docker\certs.d\ca.pem
False

2 Comments

As per @TheMadTechnician, you should do it the right way: $json | ForEach {$Key = $_.psobject.properties.name; Test-Path -Path $_.$Key} Your stalling the pipeline if you first investigate all the objects with odd regular expressions and than start releasing the actual objects.
I get that. Yet, again, as I originally stated in my original post to the OP on the other forum, There are several ways to go about this, and I just gave him one way and openly admitted it was not the most elegant. So, I gave and approach, TheMadTechnician gave and approach, still using RegEx btw, and you have give the OP another option. Choices, choices, choices.
0

You can index into the array. Check out $json.GetType()

$jsonArray ='[{"privateKeyLocation" : "C:\\ProgramData\\docker\\certs.d\\key.pem"},
{"publicKeyLocation" : "C:\\ProgramData\\docker\\certs.d\\cert.pem"},
{"publicKeyCALocation" : "C:\\ProgramData\\docker\\certs.d\\ca.pem"}]'
$json = convertfrom-json $jsonArray

foreach($i in 0..($json.Count-1)){
    $json[$i] | out-host
    $i++
}

1 Comment

What out-host does which Write-Output does not? I can not output those objects if I replace $json[$i] | out-host with write-output $json[$i]
0

You can use ForEach-Object i.e:

$json | ForEach-Object -Process { Write-Hoste $_; }

That's I believe the simplest way and gives you easy access to properties if array contains objects with other properties.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.