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.