I query a REST API which returns a JSON with many subproperties that I need to extract using a calculated property hashtable with Select-Object
. The subproperty has always the same name (result
), and I want to use a filter or a function to avoid rewriting the hashtable all along.
Example:
some json | ConvertFrom-Json | Select-Object @{ Name="name"; Expression={ $_."name".result}}, @{ Name="year"; Expression={ $_."year".result}}, etc.
I thought it will be more convenient (and easy) to build a filter that would return the hashtable:
filter Get-Result {
param (
[string]$field
)
@{N=$field; E={ $_."$field".result}}
}
But $field is not replaced in Expression block. I don't understand why
> Get-Result -field "year"
Name Value
---- -----
N year
E $_."$field".result
How to make $_."$field".result
be $_."year".result
in Expression block?
Two options: closure or source code generation
You can call GetNewClosure()
on the scriptblock passed to the Expression
entry - this will cause PowerShell's internal compiler to create a dynamic module that "remembers" the value bound to $fieldName
(or any other variable references to the scope where GetNewClosure()
is called):
# create some dummy objects for testing
$objects = 1..10|%{
[pscustomobject]@{
name = @{ result = "Name_${_}" }
year = @{ result = Get-Random -Minimum 1900 -Maximum 2025 }
}
}
# define filter to generate property expression tables
filter Get-Result {
# to take advantage of pipeline input, use `$_`
$fieldName = "$_"
# return property table with closure attached
@{N=$fieldName; E={ $_."$fieldName".result}.GetNewClosure()}
}
# this now works as expected
$objects |Select-Object -Property ('name','year' |Get-Result)
The alternative is to construct the expression block by generating its source code and then passing that off to [scriptblock]::Create()
:
filter Get-Result {
# to take advantage of pipeline input, use `$_`
$fieldName = "$_"
# escape any literal `'`s
$escapedName = [System.Management.Automation.Language.CodeGeneration]::EscapeSingleQuotedStringContent($fieldName)
$expressionText = '$_.''{0}''.result' -f $escapedName
$expression = [scriptblock]::Create($expressionText)
# return property table with closure attached
@{N=$fieldName; E=$expression}
}