Say I have JSON like:
{
\"a\" : {
\"b\" : 1,
\"c\" : 2,
}
}
Now ConvertTo-Json
will happily cr
No, there is no special syntax.
While the simplest solution is to use Invoke-Expression
, as shown next, Invoke-Expression should generally be avoided; it is safe in this particular scenario, because you fully control the input string, but it is better to form a habit of not using Invoke-Expression
, especially given that in most situations there are alternatives that are both more robust and more secure:
$json = @'
{
"a" : {
"b" : 1,
"c" : 2,
}
}
'@
$obj = ConvertFrom-Json $json
# The path to the target property.
$propertyPath = 'a.b'
# NOTE: In general, AVOID Invoke-Expression
# Construct the expression and pass it to Invoke-Expression.
# Note the need to `-escape the `$` in `$obj` to prevent premature expansion.
Invoke-Expression "`$obj.$propertyPath"
The above is the equivalent of executing $obj.a.b.
directly and yields 1
.
Alternatively, you could write a simple helper function:
function propByPath($obj, $propertyPath) {
foreach ($prop in $propertyPath -split '\.') { $obj = $obj.$prop }
$obj # output
}
Instead of the Invoke-Expression
call you would then use:
propByPath $obj $propertyPath
You could even use PowerShell's ETS (extended type system) to attach a .GetPropByPath()
method to all [pscustomobject]
instances (PSv3+ syntax; in PSv2 you'd have to create a *.types.ps1xml
file and load it with Update-TypeData -PrependPath
):
'System.Management.Automation.PSCustomObject',
'Deserialized.System.Management.Automation.PSCustomObject' |
Update-TypeData -TypeName { $_ } `
-MemberType ScriptMethod -MemberName GetPropByPath -Value { #`
param($propPath)
$obj = $this
foreach ($prop in $propPath -split '\.') { $obj = $obj.$prop }
$obj # output
}
You could then call $obj.GetPropByPath('a.b')
.
Note: Type Deserialized.System.Management.Automation.PSCustomObject
is targeted in addition to System.Management.Automation.PSCustomObject
in order to also cover deserialized custom objects, which are returned in a number of scenarios, such as using Import-CliXml
, receiving output from background jobs, and using remoting.
.GetPropByPath()
will be available on any [pscustomobject]
instance in the remainder of the session (even on instances created prior to the Update-TypeData
call [1]); place the Update-TypeData call in your $PROFILE
(profile file) to make the method available by default.
A more robust solution that supports indexing and preserves array-valued properties as such
The above solution:
'a.b[2]'
)The following solution fixes these limitations, but note that:
Only literal, scalar indices are supported (that is, you can use 'a.b[2]'
, but not 'a.b[1..2]'
or 'a.b[1, 2]'
, for instance)
For properties that are hashtables, specify the (literal) key name without embedded quoting (e.g., 'a.ht[bar]'
); note that you won't be able to access numeric hashtable keys in general, and, additionally, you won't be able to access an ordered hashtable's entries by index.
'System.Management.Automation.PSCustomObject',
'Deserialized.System.Management.Automation.PSCustomObject' |
Update-TypeData -TypeName { $_ } `
-MemberType ScriptMethod -MemberName GetPropByPath -Value { #`
param($propPath)
$obj = $this
foreach ($prop in $propPath -split '\.') {
# See if the property spec has an index (e.g., 'foo[3]')
if ($prop -match '(.+?)\[(.+?)\]$') {
$obj = $obj.($Matches.1)[$Matches.2]
} else {
$obj = $obj.$prop
}
}
# Output: If the value is a collection (array), output it as a
# *single* object.
if ($obj.Count) {
, $obj
} else {
$obj
}
}
[1] Verify with (all on one line) $co = New-Object PSCustomObject; Update-TypeData -TypeName System.Management.Automation.PSCustomObject -MemberType ScriptMethod -MemberName GetFoo -Value { 'foo' }; $co.GetFoo()
, which outputs foo
even though $co
was created before Update-TypeData
was called.