1

The following object is a toy example to illustrate my question:

$food = @(
    [PSCustomObject]@{Category = "Fruits"; Items = @("Mango", "Pineapple", "Orange", "Tangerine")},
    [PSCustomObject]@{Category = "Cities"; Items = @("Abidjan", "Douala")},
    [PSCustomObject]@{Category = "Animals"; Items = @("Dog", "Bird", "Cat")}
)

$food

Category Items
-------- -----
Fruits   {Mango, Pineapple, Orange, Tangerine}
Cities   {Abidjan, Douala}
Animals  {Dog, Bird, Cat}

I would like to access all items of the "Fruits" category, for example. I tried to first determine the index of "Fruits" in the Category property then use it in the "Items" property. I thought that the entire array of fruit names would be returned; however, it did not work as expected:

$i = $food.Category.IndexOf("Fruits")
$food[$i]
$food.Items[$i]

Mango

How should I go about it?

Thank you.

SavedByJESUS
  • 3,262
  • 4
  • 32
  • 47

2 Answers2

1

You need to apply index $i to the $food array itself, not to .Items:

$food[$i].Items # -> @('Mango', 'Pineapple', 'Orange', 'Tangerine')

However, a more PowerShell-idiomatic solution, using the Where-Object cmdlet to perform the category filtering (with simplified syntax), would be:

($food | Where-Object Category -eq Fruits).Items

Note:

  • The above could potentially return multiple elements from the $food array, namely if more than one element has a .Category property value 'Fruits'.

  • To limit output to the first element, you could append a Select-Object -First 1 pipeline segment, but there's a simpler alternative: the intrinsic .Where() method has an overload that allows you to stop at the first match:[1]

    $food.Where({ $_.Category -eq 'Fruits' }, 'First').Item
    
    • It is somewhat unfortunate that the Where-Object cmdlet doesn't have an analogous feature; GitHub issue #13834 suggests enhancing the Where-Object cmdlet to ensure feature parity with its method counterpart, .Where().

As for what you tried:

$food.Items[$i]

Accessing .Items on the array stored in $food performs member-access enumeration.

That is, the .Items property on each object stored in the array is accessed, and the resulting values are returned as a single, flat array of all such property values.

  • To spell it out: $food.Items returns the following array:

    @('Mango', 'Pineapple', 'Orange', 'Tangerine', 'Abidjan', 'Douala', 'Dog', 'Bird', 'Cat')
    
  • As an aside: Your index-finding code, $food.Category.IndexOf("Fruits"), also makes use of member-access enumeration, relying on returning the .Category property values across all elements of the $food array.

Indexing into the resulting array (with [$i], with $i being 0) then only extracts its first element, i.e. 'Mango'.


[1] Using the .Where() method has additional implications: (a) the input object(s) must be collected in full, up front, and (b) what is output is invariably a collection, namely of type [System.Collections.ObjectModel.Collection[psobject]] - see this answer for details.

mklement0
  • 382,024
  • 64
  • 607
  • 775
1

You need to loop over each element in the $food array to find the object having a property with Category equal to Fruits then expand the Items property. The 3 most commonly used filtering techniques in PowerShell are:

  1. Where-Object:

    $food | Where-Object Category -EQ Fruits |
       ForEach-Object Items
    
  2. .Where Method:

    $food.Where({ $_.Category -eq 'Fruits' }).Items
    
  3. A classic foreach loop with an if condition:

    foreach ($item in $food) {
        if ($item.Category -eq 'Fruits') {
            $item.Items
        }
    }
    
Santiago Squarzon
  • 41,465
  • 5
  • 14
  • 37