Private/Select-ObjectLike.ps1
function Select-ObjectLike { <# .SYNOPSIS Filters an array of objects using a flexible hashtable-based filter syntax. .DESCRIPTION Filters objects (including nested arrays and properties) using a hashtable filter. Supports exact match, comparison operators (>, <, >=, <=, ==, !=, gt, lt, ge, le, eq, ne), and regex matching (match, notmatch). Nested filters are supported for subobjects and arrays. Returns all objects that match the filter criteria. .PARAMETER InputObject The array of objects to filter. .PARAMETER Filter A hashtable describing the filter. Keys are property names, values can be: - a literal value (for exact match) - a hashtable with 'op' and 'value' keys for operator-based filtering - a nested hashtable for subobject/array filtering .FILTER STRUCTURE # Exact match: $filter = @{ tag_name = 'v5.0.2' } # Comparison: $filter = @{ published_at = @{ op = 'gt'; value = '2025-01-01' } } $filter = @{ size = @{ op = 'le'; value = 100000000 } } # Regex: $filter = @{ tag_name = @{ op = 'match'; value = '^v5\.' } } $filter = @{ tag_name = @{ op = 'notmatch'; value = '^v4\.' } } # Nested/array: $filter = @{ assets = @{ name = 'Firebird-5.0.2.1613-0-android-arm32.tar.gz' } } $filter = @{ assets = @{ size = @{ op = 'gt'; value = 100000000 } } } .EXAMPLE $result = Select-ObjectLike -InputObject $releases -Filter @{ tag_name = @{ op = 'match'; value = '^v5\.' } } # Returns all releases with tag_name starting with 'v5.' .EXAMPLE $result = Select-ObjectLike -InputObject $releases -Filter @{ assets = @{ size = @{ op = 'gt'; value = 100000000 } } } # Returns all releases with at least one asset over 100MB .OUTPUTS PSCustomObject[] An array of objects matching the filter criteria. #> [CmdletBinding()] param ( [Parameter(Mandatory, ValueFromPipeline)] [object]$InputObject, [Parameter(Mandatory)] [hashtable]$Filter ) process { foreach ($item in $InputObject) { $match = $true foreach ($key in $Filter.Keys) { $filterValue = $Filter[$key] $itemValue = $item.$key if ($null -eq $itemValue) { $match = $false break } if ($filterValue -is [hashtable] -and $filterValue.ContainsKey('op')) { # Operator-based filter $op = $filterValue['op'] $val = $filterValue['value'] if ($itemValue -is [datetime]) { $itemValue = [datetime]$itemValue $val = [datetime]$val } switch ($op) { '>' { if (!($itemValue -gt $val)) { $match = $false; break } } '<' { if (!($itemValue -lt $val)) { $match = $false; break } } '>=' { if (!($itemValue -ge $val)) { $match = $false; break } } '<=' { if (!($itemValue -le $val)) { $match = $false; break } } '==' { if (!($itemValue -eq $val)) { $match = $false; break } } '!=' { if (!($itemValue -ne $val)) { $match = $false; break } } 'gt' { if (!($itemValue -gt $val)) { $match = $false; break } } 'lt' { if (!($itemValue -lt $val)) { $match = $false; break } } 'ge' { if (!($itemValue -ge $val)) { $match = $false; break } } 'le' { if (!($itemValue -le $val)) { $match = $false; break } } 'eq' { if (!($itemValue -eq $val)) { $match = $false; break } } 'ne' { if (!($itemValue -ne $val)) { $match = $false; break } } 'match' { if ($itemValue -notmatch $val) { $match = $false; break } } 'notmatch' { if ($itemValue -match $val) { $match = $false; break } } default { $match = $false; break } } } elseif ($filterValue -is [hashtable]) { # Nested filter for subobjects/arrays if ($itemValue -is [System.Collections.IEnumerable] -and !$itemValue.GetType().IsPrimitive) { $subMatch = $false foreach ($sub in $itemValue) { if (Select-ObjectLike -InputObject @($sub) -Filter $filterValue) { $subMatch = $true break } } if (-not $subMatch) { $match = $false; break } } else { # Exact match for hashtable if ($itemValue -ne $filterValue) { $match = $false; break } } } else { # Exact match if ($itemValue -ne $filterValue) { $match = $false; break } } } if ($match) { $item } } } } |