Search code examples
powershellhashtablepscustomobject

How do I convert a powershell hashtable to an object?


Some hashtables in PowerShell, such as those imported with Import-PowerShellDataFile, would be much easier to navigate if being a PSCustomObject instead.

@{
    AllNodes = @(
        @{
            NodeName = 'SRV1'
            Role = 'Application'
            RunCentralAdmin = $true
        },
        @{
            NodeName = 'SRV2'
            Role = 'DistributedCache'
            RunCentralAdmin = $true
        },
        @{
            NodeName = 'SRV3'
            Role = 'WebFrontEnd'
            PSDscAllowDomainUser = $true
            PSDscAllowPlainTextPassword = $true
            CertificateFolder = '\\mediasrv\Media'
        },
        @{
            NodeName = 'SRV4'
            Role = 'Search'
        },
        @{
            NodeName = '*'
            DatabaseServer = 'sql1'
            FarmConfigDatabaseName = '__FarmConfig'
            FarmContentDatabaseName = '__FarmContent'
            CentralAdministrationPort = 1234
            RunCentralAdmin = $false
        }
    );
    NonNodeData = @{
        Comment = 'No comment'
    }
}

When imported it will become a hashtable of hashtables

$psdnode = Import-PowerShellDataFile .\nodefile.psd1

$psdnode

Name                           Value
----                           -----
AllNodes                       {System.Collections.Hashtable, System.Collect...
NonNodeData                    {Comment}

$psdnode.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Hashtable                                System.Object

and the data structure will be just weird when navigating by property name.


Solution

  • There's good information in the existing answers, but given your question's generic title, let me try a systematic overview:

    • You do not need to convert a hashtable to a [pscustomobject] instance in order to use dot notation to drill down into its entries (properties), as discussed in the comments and demonstrated in iRon's answer.

      • A simple example:

        @{ top = @{ nested = 'foo' } }.top.nested  # -> 'foo'
        
      • See this answer for more information.

    • In fact, when possible, use of hashtables is preferable to [pscustomobject]s, because:

      • they are lighter-weight than [pscustomobject] instances (use less memory)
      • it is easier to construct them iteratively and add / remove entries on demand.

    Note:


    In cases where you do need to convert a [hasthable] to a [pscustomobject]:

    While many standard cmdlets accept [hasthable]s interchangeably with [pscustomobjects]s, some do not, notably ConvertTo-Csv and Export-Csv (see GitHub issue #10999 for a feature request to change that); in such cases, conversion to [pscustomobject] is a must.

    Caveat: Hasthables can have keys of any type, whereas conversion to [pscustomobject] invariably requires using string "keys", i.e. property names. Thus, not all hashtables can be faithfully or meaningfully converted to [pscustomobject]s.

    • Converting non-nested hashtables to [pscustomobject]:

      • The syntactic sugar PowerShell offers for [pscustomobject] literals (e.g., [pscustomobject] @{ foo = 'bar'; baz = 42 }) also works via preexisting hash; e.g.:

        $hash = @{ foo = 'bar'; baz = 42 } 
        $custObj = [pscustomobject] $hash   # Simply cast to [pscustomobject]
        
    • Converting nested hashtables, i.e. an object graph, to a [pscustomobject] graph:

      • A simple, though limited and potentially expensive solution is the one shown in your own answer: Convert the hashtable to JSON with ConvertTo-Json, then reconvert the resulting JSON into a [pscustomobject] graph with ConvertFrom-Json.

        • Performance aside, the fundamental limitation of this approach is that type fidelity may be lost, given that JSON supports only a few data types. While not a concern with a hashtable read via Import-PowerShellDataFile, a given hashtable may contain instances of types that have no meaningful representation in JSON.
      • You can overcome this limitation with a custom conversion function, ConvertFrom-HashTable (source code below); e.g. (inspect the result with Format-Custom -InputObject $custObj):

        $hash = @{ foo = 'bar'; baz = @{ quux = 42 } } # nested hashtable
        $custObj = $hash | ConvertFrom-HashTable # convert to [pscustomobject] graph
        

    ConvertFrom-HashTable source code:

    Note: Despite the name, the function generally supports instance of types that implement IDictionary as input.

    function ConvertFrom-HashTable {
      param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [System.Collections.IDictionary] $HashTable
      )
      process {
        $oht = [ordered] @{} # Aux. ordered hashtable for collecting property values.
        foreach ($entry in $HashTable.GetEnumerator()) {
          if ($entry.Value -is [System.Collections.IDictionary]) { # Nested dictionary? Recurse.
            $oht[[object] $entry.Key] = ConvertFrom-HashTable -HashTable $entry.Value # NOTE: Casting to [object] prevents problems with *numeric* hashtable keys.
          } else { # Copy value as-is.
            $oht[[object] $entry.Key] = $entry.Value
          }
        }
        [pscustomobject] $oht # Convert to [pscustomobject] and output.
      }
    }