I have a function that converts a PSObject into a hashtable. Function works well, but there's a little subtlety that I am trying to understand and can't really grasp my head around.
I'm using PowerShell Core 7.0.3
The function:
function Convert-PSObjectToHashtable
{
param (
[Parameter(ValueFromPipeline)]
$InputObject
)
process
{
if ($null -eq $InputObject) { return $null }
if ($InputObject -is [System.Collections.IEnumerable] -and $InputObject -isnot [string])
{
$collection = @(
foreach ($object in $InputObject) { Convert-PSObjectToHashtable $object }
)
# buggy
#Write-Output -NoEnumerate $collection
# correct
$collection
}
elseif ($InputObject -is [psobject])
{
$hash = @{}
foreach ($property in $InputObject.PSObject.Properties)
{
$hash[$property.Name] = Convert-PSObjectToHashtable $property.Value
}
$hash
}
else
{
$InputObject
}
}
}
I execute the following code:
$obj = "{level1: ['e','f']}"
$x = $obj | ConvertFrom-Json | Convert-PSObjectToHashtable
[Newtonsoft.Json.JsonConvert]::SerializeObject($x)
The "buggy" code returns me:
{"level1":{"CliXml":"<Objs Version=\"1.1.0.1\" xmlns=\"http://schemas.microsoft.com/powershell/2004/04\">\r\n <Obj RefId=\"0\">\r\n <TN RefId=\"0\">\r\n <T>System.Object[]</T>\r\n <T>System.Array</T>\r\n <T>System.Object</T>\r\n </TN>\r\n <LST>\r\n <S>e</S>\r\n <S>f</S>\r\n </LST>\r\n </Obj>\r\n</Objs>"}}
The correct code returns me:
{"level1":["e","f"]}
Why wouldn't the buggy code work, if technically, in PowerShell when working with the object result, they look equivalent?
Thank you!
-noenumerate. Using a variable as output unrolls the array. So each array element is sent to the pipeline. When-noenumerateis used, the array as a single object is sent to the pipeline. If you remove-noenumerate, thenwrite-outputwill work the same as the variable.-NoEmumerateand leavingWrite-Output $collectionand I get the same "buggy" output.-is [System.Management.Automation.PSCustomObject]--is [psobject]does not work reliably, and neither does-is [pscustomobject], because - confusingly -[psobject]and[pscustomobject]both refer to[System.Management.Automation.PSObject], and incidentallyPSObject-wrapped objects of any type, as explained in Mathias' answer, therefore also report$truefor-is [psobject]- see github.com/PowerShell/PowerShell/issues/11921$collectionis[object[]]-typed -$collection(letting the engine enumerate and collect in a new[object[]]array),Write-Output $collection(ditto) andWrite-Output -NoEnumerate $collection(output the array as a whole) all happen to have the same effect - except that use of a cmdlet, such asWrite-Outputhere, creates normally invisible[psobject]wrappers, which happen to surface in the[Newtonsoft.Json.JsonConvert]::SerializeObject()call, as explained in Mathias' answer.