I am calling an API that returns 3 values in an object "tags", which has values for "tags.name" and tags.results". The values below are what is returned. But as I try to navigate through the values, I can see that the "null" value, the {}, was not stored in the array. It just skipped it, but I need this value as it completely ruins the array.
Any idea how to correctly populate the array so it doesn't skip this {} value?
Response Values
values attributes
------ ----------
{1605560351000 78.129448 3} @{machine_type=System.Object[]}
{}
{1605560354000 0 3} @{machine_type=System.Object[]}
Resulting Array
0 : 1605560351000 78.129448 3
1 : 1605560354000 0 3
2 :
The PowerShell nvoke and array code:
$response = Invoke-RestMethod 'https://api' -Method 'POST' -Headers $headers -Body $body
"Response Values"
$response.tags.results
""
"Resulting Array"
"0 : " + $response.tags.results.values[0]
"1 : " + $response.tags.results.values[1]
"2 : " + $response.tags.results.values[2]
The returned JSON from Invoke-RestAPI. You can see where the returned value is null for the second node.
{
"tags": [
{
"name": "StateComp1",
"results": [
{
"values": [
[
1605561152000,
75.436455,
3
]
],
}
],
},
{
"name": "StateComp2",
"results": [
{
"values": [],
}
],
},
{
"name": "StateComp3",
"results": [
{
"values": [
[
1605561469000,
0,
3
]
],
}
],
}
]
}
The problem is not specific to Invoke-RestMethod
and is instead explained by the behavior of PowerShell's member-access enumeration feature:
When you access $response.tags.results.values
, the .values
properties on the - multiple - .results
properties (you're using nested member-access enumeration) are effectively enumerated as follows:
$response.tags.results | ForEach-Object { $_.values }
In doing so, empty arrays are effectively removed from the output, and you get only 2 output objects (that each contain the inner array of the nested ones in your case); you can verify that by applying (...).Count
to the command above.
The reason is that objects output by a script block ({ ... }
) are sent to the pipeline, which by default enumerates objects that are collections (arrays).
Since your 2nd .values
value is an empty array (parsed from JSON []
and therefore not $null
), there is nothing to enumerate and nothing is output, resulting in effective removal.
Note that the above implies that collections of collections are flattened by member-access enumeration; for instance, if the property values are 3 2-element arrays, the pipeline-enumeration logic results in a single 6-element array rather than in a 3-element array containing 2-element arrays each.
The workaround is to to use the ForEach()
array method, which doesn't perform this stripping if you target the property by supplying its name as a string:
$values = $response.tags.results.ForEach('values')
@"
Resulting Array"
0 : $($values[0])
1 : $($values[1])
2 : $($values[2])
"@
Note that your non-empty .values
properties are actually nested arrays; to shed the outer array, use $values[0][0]
, $values[1][0]
, and $values[2][0]
.
Caveat: The workaround is only effective if you access the property by name string - .ForEach('values')
; the seemingly equivalent script-block based command,
.ForEach({ $_.values })
again removes empty arrays, like member-access enumeration and the ForEach-Object
cmdlet; alternatively, however, you can work around that by wrapping the script-block output in an auxiliary, temporary single-element array that preserves the original arrays, using the unary form of ,
the array constructor operator:
.ForEach({ , $_.values })
You could also use the same technique with the - slower - ForEach-Object
cmdlet.