Powershell showing wrong output for JSON-object and ADDRESS-property

1.1k views Asked by At

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?

1

There are 1 answers

0
mklement0 On BEST ANSWER

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.)

That PowerShell provides no distinct operator syntax to distinguish between direct member access and member-access enumeration is the subject of [GitHub issue #7445](https://github.com/PowerShell/PowerShell/issues/7445), but the existing behavior is unlikely to change.

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.