I am currently struggling to convert a JSON-string into an array of objects and to GENERALLY handle the properties/attributes of each object.
Here is a simple demo, that shows that e.g. the attribute "address" seems to be a bit special:
cls
$json = '[{"id":"1","address":"1"},{"id":"2","address":"2"}]'
$list = $json | ConvertFrom-Json
$list.id # OK
$list.address # gives a weired result - is this a bug?
$list.GetEnumerator().address # that works
This is the output:
1
2
OverloadDefinitions
-------------------
System.Object&, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Address(int )
1
2
As you can see, I need to add ".GetEnumerator()" to get the correct "address"-values.
Is this the expected? Should I ALWAYS use the ".GetEnumerator()" to be safe?
Since $list
is an array (collection), using something like $list.id
performs member-access enumeration; that is, the .id
property is automatically accessed on each element, and the resulting values are returned as an array ([object[]]
)[1].
However, if the array / collection type itself has a member by that name (a property or method), it takes precedence, which is what happened in your case: .NET arrays have an .Address
method (that is added by the runtime - see this answer), and that is what preempted accessing the elements' .Address
property.
(What you saw was PowerShell's representation of the method's signature (overload), which is what you get when you access a method by name only, without actually calling it by appending ((...)
); try 'foo'.ToUpper
, fo instance.)
The most efficient way to work around that problem is to use the PSv4+ .ForEach()
array method, which always targets the collection's elements:
$list = '[{"id":"1","address":"1"},{"id":"2","address":"2"}]' | ConvertFrom-Json
$list.ForEach('address')
Caveat: If you're running Windows PowerShell and $list
can situationally just result in a single pscustomobject
rather than an array, you must enclose $list
in @(...)
, the array-subexpression operator, to ensure that the .ForEach()
method is available. This is a [pscustomobject]
-specific bug (given that even single objects should consistently have a .ForEach()
method), which has been fixed in PowerShell (Core) 6+.
# @(...) is necessary in Windows PowerShell only.
@($list).ForEach('address')
Note:
Technically, this returns a [System.Collections.ObjectModel.Collection[PSObject]]
collection, but in most cases you can use it like a regular [object[]]
PowerShell array.
Unlike with member-access enumeration, a collection instance is also returned if there's only one element in the input collection (rather than returning that one element's property value as-is, the way member-access enumeration does).
In PSv3- you can use the ForEach-Object
cmdlet, or Select-Object -ExpandProperty
:
$list | ForEach-Object address # PSv2: | ForEach-Object { $_.address }
# OR
$list | Select-Object -ExpandProperty address
[1] If there's only one element in the collection, its property value is returned as-is, not wrapped in a (single-element) array, which is the same logic that is applied when collecting pipeline output in a variable. That this logic may be unexpected in the context of member-access enumeration, which is an expression context, is being discussed in GitHub issue #6802.