v7.0 added support to Select-Object
for treating the entries of [hashtable]
input objects like properties that can be selected; e.g.:
# v7+ only:
# In v6-, this would only work with a [pscustomobject] "cast".
PS> @{ one = 1; two = 2; three = 3 } | Select-Object three, one
three one
----- ---
3 1
This interchangeable use of hashtables and (custom) objects is useful, and also implemented in other cmdlets, such as ConvertTo-Json
.
As a user, I'd like to see the same support implemented for _other dictionary-like types_ as well, notably _ordered_ hashtables and generic dictionaries:
# WISHFUL THINKING - does NOT currently work as expected (the named entries' values aren't extracted).
PS> [ordered] @{ one = 1; two = 2; three = 3 } | Select-Object three, one
three one
----- ---
3 1
# WISHFUL THINKING - does NOT currently work as expected (the named entries' values aren't extracted).
PS> $gd = [System.Collections.Generic.Dictionary[string, object]]::new(); $gd.Add('one', 1); $gd.Add('two', 2); $gd.Add('three', 3); $gd | Select-Object three, one
three one
----- ---
3 1
Backward compatibility considerations:
While technically a breaking change - currently _only_ the dictionary's _own_ properties rather than its entries are selected from - it is the same change that has already been deemed acceptable for [hashtable]
instances.
The dictionary's own properties will still be selectable, but will be shadowed by entries with keys of the same name.
Support treating the entries of any input type that implements IDictionary
as properties.
Should be pretty straightforward to do... Probably just swap out some Hashtable
for IDictionary
in the code paths here. I might take a quick look, see what's needed for this to work.
Looks like the main pain point will be here:
Names and comments around these code paths will need to be updated too, but that's probably the most questionable part of it, I'm not yet sure if just passing in the wrapped dictionary is enough, I'll have to investigate the languageprimitives method as well.
Fairly doable, there's a good trail there, I'll have a look if I get the time, but that's the main bit if anyone else wants to have a crack at it.
GitHubPowerShell for every system! Contribute to PowerShell/PowerShell development by creating an account on GitHub.
I did a bit more digging, and it looks like this might be a tad complex to sort out. Mainly because the handler for this conversion is in LanguagePrimitives, and I'm unsure whether enhancing that to allow conversion from any IDictionary to PSCustomObject is acceptable for this case, it's a bit of a broader stroke that might suddenly make PSCustomObject _casts_ also work that way 馃
it's a bit of a broader stroke that might suddenly make PSCustomObject _casts_ also work that way 馃
That'd be pretty dope tho 馃槷
Only thing it could break (that I can think of in the limited capacity I've thought about it) is if someone for some reason was trying to simply wrap a dictionary in a PSObject
by casting to PSCustomObject
.
I'd imagine one or two other code paths might need some small tweaks, but yeah that's within the realm of possibility for sure. Just a matter of being aware that PSObject properties expect strings and if you have a dictionary with non-string keys it might do weird things or not work. 馃し
To be fair, hashtable
is basically Dictionary<object, object>
so that's already an issue 馃榾
You know, that's a good point. Maybe we should go down that route then.... 馃槀
I agree that also being able to cast any dictionary to a custom object would be helpful, and note that it already works for [hashtable]
and ordered hashtables (System.Collections.Specialized.OrderedDictionary
), but not for System.Collections.Generic.Dictionary[object, object]
.
# Note: If you cast to [psobject] instead of [pscustomobject], all types are effectively retained (but
# wrapped in a typically invisible [psobject] instance).
PS> @{ one = 1; two = 2 },
[ordered] @{ one = 1; two = 2 },
$($gd = [System.Collections.Generic.Dictionary[object, object]]::new(); $gd.Add('one', 1); $gd.Add('two', 2); $gd) |
foreach { ([pscustomobject] $_).GetType().Name }
PSCustomObject
PSCustomObject
Dictionary`2
As for what could break, @SeeminglyScience: It's an exotic scenario to begin with, but given that there seems to be built-in magic to distinguish between a [psobject]
and a [pscustomobject]
cast (even though _both_ accelerators refer to System.Management.Automation.PSObject
), one would certainly hope that someone who did that used [psobject]
, not [pscustomobject]
, for conceptual clarity alone.
As for future dope, if you will: being able to cast [pscustomobject]
instances back to [hashtable]
or (more awkwardly, but more usefully) to [System.Collections.Specialized.OrderedDictionary]
or even [System.Collections.Generic.Dictionary[string, object]]
would be nice as well.
As for what could break, @SeeminglyScience: It's an exotic scenario to begin with, but given that there seems to be built-in magic to distinguish between a
[psobject]
and a[pscustomobject]
cast (even though _both_ accelerators refer toSystem.Management.Automation.PSObject
), one would certainly hope that someone who did that used[psobject]
, not[pscustomobject]
, for conceptual clarity alone.
Yeah I agree. The chances that someone knows enough to know that wrapping in pso's is a thing, has a use case for doing it explicitly, and still uses PSCustomObject
instead of psobject
, are incredibly slim.
Allowing implicit conversion from any dictionary to a custom object sounds like a great idea, we already support that for concrete target types:
PS ~> class T { [string]$A; [int]$B }
PS ~> $dict = [System.Collections.Generic.Dictionary[object,object]]::new()
PS ~> $dict['A'] = 'Some String'
PS ~> $dict['B'] = 123
PS ~> $dict -as [T]
A B
- -
Some String 123
.... but I'm not sure I'm convinced about selecting dictionary entries with Select-Object
- how would you resolve @{Keys = '123'} |Select-Object Keys
?
@IISResetMe It already works for hashtable's (added in 7.0). This issue is just about extending it to any other IDictionary
. Currently it works the same as normal property binding (e.g. it'll return 123
in that example).
@IISResetMe
we already support that for concrete target types:
Neat - didn't know that.
To add to @SeeminglyScience's comment:
Indeed, the decision has already been made to _shadow_ the dictionary's own properties - which is the sensible choice in my view - after all, if you send an object to Select-Object
, your intent is to select from its _members_ and it makes sense to conceive of the dictionary _entries_ as such.
To get the dictionary's own .Keys
property value, just call $dict.Keys
or - if you're really sending multiple dictionaries to the pipeline whose own properties you want to query, $dict, $dict | % { $_.Keys }
Note, however, that it is the inverse of how it works in member enumeration (see #7445):
PS> ([pscustomobject] @{ count = 10 }, [pscustomobject] @{ count = 20 }).Count
2 # collection's own property took precedence
Just to tie this to the earlier, related proposal to make Export-Csv
/ ConvertTo-Csv
support IDictionary
input meaningfully too: #10999
Aye, I've already implemented that one, think it's just waiting for @SteveL-MSFT or someone else to give #11029 a look over. I was hoping to get it in for 7.1, but I guess it's waited too long for that now. 馃檪
Would be nice to have this as a complement to it as well, but that'll be a separate of course.
Most helpful comment
@IISResetMe It already works for hashtable's (added in 7.0). This issue is just about extending it to any other
IDictionary
. Currently it works the same as normal property binding (e.g. it'll return123
in that example).