PSCustomObject to Hashtable

Какой самый простой способ конвертировать PSCustomObject к Hashtable? Он отображается так же, как оператор с символом "сплат", фигурными скобками и парами "ключ-значение". Когда я пытаюсь привести его к [Hashtable] это не работает Я тоже пробовал .toString() и назначенная переменная говорит, что это строка, но ничего не отображает - есть идеи?

10 ответов

Решение

Не должно быть слишком сложно. Нечто подобное должно сработать:

# Create a PSCustomObject (ironically using a hashtable)
$ht1 = @{ A = 'a'; B = 'b'; DateTime = Get-Date }
$theObject = new-object psobject -Property $ht1

# Convert the PSCustomObject back to a hashtable
$ht2 = @{}
$theObject.psobject.properties | Foreach { $ht2[$_.Name] = $_.Value }

Кит уже дал вам ответ, это просто еще один способ сделать то же самое с однострочным:

$psobject.psobject.properties | foreach -begin {$h=@{}} -process {$h."$($_.Name)" = $_.Value} -end {$h}

Вот версия, которая работает также с вложенными хеш-таблицами / массивами (что полезно, если вы пытаетесь сделать это с помощью DSC ConfigurationData):

function ConvertPSObjectToHashtable
{
    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) { ConvertPSObjectToHashtable $object }
            )

            Write-Output -NoEnumerate $collection
        }
        elseif ($InputObject -is [psobject])
        {
            $hash = @{}

            foreach ($property in $InputObject.PSObject.Properties)
            {
                $hash[$property.Name] = ConvertPSObjectToHashtable $property.Value
            }

            $hash
        }
        else
        {
            $InputObject
        }
    }
}

Мой крайне ленивый подход, реализованный благодаря новой функции PowerShell 6:

$myhashtable = $mypscustomobject | ConvertTo-Json | ConvertFrom-Json -AsHashTable

Это работает для объектов PSCustomObject, созданных ConvertFrom_Json.

Function ConvertConvertFrom-JsonPSCustomObjectToHash($obj)
{
    $hash = @{}
     $obj | Get-Member -MemberType Properties | SELECT -exp "Name" | % {
                $hash[$_] = ($obj | SELECT -exp $_)
      }
      $hash
}

Отказ от ответственности: я почти не понимаю PowerShell, так что это, вероятно, не так чисто, как могло бы быть. Но это работает (только для одного уровня).

Мой код:

function PSCustomObjectConvertToHashtable() {
    param(
        [Parameter(ValueFromPipeline)]
        $object
    )

    if ( $object -eq $null ) { return $null }

    if ( $object -is [psobject] ) {
        $result = @{}
        $items = $object | Get-Member -MemberType NoteProperty
        foreach( $item in $items ) {
            $key = $item.Name
            $value = (PSCustomObjectConvertToHashtable -object $object.$key)
            $result.Add($key, $value)
        }
        return $result
    } else {
        return $object
    }
}

По предложению Дейва , вот действительно универсальная функция преобразования. Это имитируетCovertTo-Json | ConvertFrom-Json -AsHashTableнамеренно только частично, потому что это имеет некоторые ограничения:

  • Частично непредвиденные отклонения результатов в зависимости от того, передается ли преобразуемый объект как параметр или через конвейер.
  • Частично раздутые результаты и плохое масштабирование с желаемой глубиной вложенности при передаче объекта по конвейеру: просто попробуйте преобразовать переданный по конвейеру объект FileInfo с помощьюDepth 6: это занимает много времени и в конце я получаюOutOfMemoryException(PowerShell 7.3, установленная память 16 ГБ).
  • Совершенно бесполезная конвертация некоторых типов: попробуйте конвертироватьXmlDocument,Int128илиBigIntобъект. Конечно, последние два не определены в соответствии со «стандартом» JSON. Но их хотя бы можно было бы преобразовать в типStringили выдать исключение.

Скрытые свойства объектов не учитываются, так как такое поведениеConvertTo-Jsonв любом случае подвергается некоторой критике . Дальнейшие пояснения можно найти в справке по функции, основанной на комментариях.

      function ConvertTo-Hashtable {
    <#
        .DESCRIPTION
            This function converts arbitrary objects into HashTables of their properties. If one of the properties is an object, the procedure is the same, up to the
            specified nesting depth. If this is reached, the properties of the object in question are no longer read out, but its ToString() method is called.

            All properties of root objects whose name does not begin with 'PS' are taken into account.
            The following properties are taken into account for all other objects:
            - Properties of the PSMemberType 'Property' unrestricted
            - Properties of the PSMemberType 'NoteProperty' only if the property value's type is PSCustomObject
            - all other PSMemberTypes only if they have the PSMemberType of the preceding property in the hierarchy (affects ScriptProperty)
            Restrictively, properties marked as 'hidden' or referencing the parent object are ignored.

            Excluded from conversion to HashTables are all objects of types for which PowerShell allows remote delivery of 'live' objects. Objects of these types are kept
            unchanged. This affects the primitive and almost primitive types, as explained at
            https://devblogs.microsoft.com/powershell/how-objects-are-sent-to-and-from-remote-sessions/. Likewise, the composition of objects in arrays (collections that
            implement IEnumerable) or associative arrays (collections that implement IDictionary) is preserved. This circumvents the JSON 'standard' restrictions on
            convertible types imposed when using ConvertFrom-Json with the -AsHashTable switch.

            A conversion of a complex object into a HashTable can be advantageous if this is to be transmitted remotely. The nesting depth of these objects is now not
            subject to any restrictions. If, on the other hand, a complex object is transmitted remotely directly, PowerShell only transfers arrays and HashTables without
            restrictions regarding their nesting depth. However, objects of a different type contained therein are only transmitted in the form of the result of their
            ToString() method reaching a nesting depth of 2. All of their properties are lost. So an Object(Arrays(Object with different properties)) becomes a
            PSObject(Arrays(String)).

            If this function is used for remote transmission, it is only necessary to ensure that a mechanism is established on the receiving side to convert the HashTable
            structure back into a 'living' object of the relevant classes. The simplest possibility is to equip these with an op_Implicit operator that takes a
            HashTable. A suitable constructor of the class must then be called within the operator. This means that automated casting, e.g. of typed arrays, is also possible.
            These links may be helpful: https://stackoverflow.com/questions/58330233/how-to-convert-pscustomobject-with-additional-props-to-a-custom-class/ and
            https://stackoverflow.com/questions/59899360/how-do-i-pass-a-class-object-in-a-argument-list-to-a-another-computer-and-call-a/76695304#76695304.

        .PARAMETER InputObject
            The object to be convertet into a HashTable. There are no type restrictions.

        .PARAMETER Depth
            The level of nesting to which the properties of objects are considered. Beyond that, its ToString() method is called.

        .PARAMETER NoEnumerate
            Specifies that output isn't enumerated. The automatic enumeration and the function of this switch is documented in the following table. It also documents
            under which circumstances the nesting depth is reduced.

                passed value            return if passed via pipeline       return if passed as parameter
                    parameter isn't set (*automatic enumeration):
                        $null                     $null                               $null
                        item                      item, depth reduced by 1            item
                        []                        $null                            *  $null
                        [$null]                *  $null                            *  $null
                        [item]                 *  item, depth reduced by 1         *  item, depth reduced by 1
                        [items]                   [items]                             [items]

                    parameter is set (*changes due to the set parameter):
                        $null                  *  [$null]                             $null
                        item                   *  [item]                              item
                        []                     *  []                               *  []
                        [$null]                *  [$null]                          *  [$null]
                        [item]                 *  [item]                           *  [item]
                        [items]                   [items]                             [items]

        .PARAMETER EnumsAsStrings
            If this switch is set, Enums will be converted to their string representation. Otherwise their numeric value will be taken.

        .LINK
            https://stackoverflow.com/questions/3740128/pscustomobject-to-hashtable
    #>
    [CmdletBinding()]
    [SuppressMessage('PSUseOutputTypeCorrectly', '', Justification = 'Returns many types')]

    param (
        [Parameter(ValueFromPipeline)] [Object]$InputObject,
        [Parameter()] [ValidateRange(1, 100)] [Int32]$Depth = 2,
        [Parameter()] [Switch]$NoEnumerate,
        [Parameter()] [Switch]$EnumsAsStrings
    )

    begin {
        function InnerConvert(
            [Object]$InputObject,
            [Int32]$Depth,
            [Int32]$DepthCtr,
            [Boolean]$EnumsAsStrings,
            [Int32]$ObjectDepthStatus,
            [PSMemberTypes]$ParentMemberType
        ) {
            if ($null -eq $InputObject) {
                $null
            } elseif ($InputObject -is [Char] -or $InputObject -is [String] -or
                $InputObject -is [Byte] -or $InputObject -is [SByte] -or $InputObject -is [Int16] -or $InputObject -is [UInt16] -or
                $InputObject -is [Int32] -or $InputObject -is [UInt32] -or $InputObject -is [Int64] -or
                $InputObject -is [Single] -or $InputObject -is [Double] -or $InputObject -is [DateTime] -or $InputObject -is [Boolean] -or
                $InputObject -is [Decimal] -or $InputObject -is [Uri] -or $InputObject -is [Xml.XmlDocument] -or
                $InputObject -is [ProgressRecord] -or $InputObject -is [TimeSpan] -or $InputObject -is [Guid] -or
                $InputObject -is [Version] -or $InputObject -is [UInt64]
            ) {
                $InputObject
            } elseif ($InputObject -is [Enum]) {
                if ($EnumsAsStrings) {
                    $InputObject.ToString()
                } else {
                    [Int32]$InputObject
                }
            } elseif ($InputObject -is [SecureString] ) {
                $InputObject | ConvertFrom-SecureString
            } elseif ($InputObject -is [UInt64] -or $InputObject.GetType().Name -in ('Int128', 'UInt128') -or $InputObject -is [BigInt]) {
                [String]$InputObject
            } else {
                if ($DepthCtr -le $Depth) {
                    if ($InputObject -is [IDictionary]) {
                        try {
                            [OrderedHashTable]$resultTable = [ordered]@{}
                        } catch {
                            [HashTable]$resultTable = @{}
                        }
                        foreach ($item in $InputObject.GetEnumerator()) {
                            $resultTable[$item.Key] = InnerConvert -InputObject $item.Value -Depth $Depth -DepthCtr ($DepthCtr + 1) `
                                -EnumsAsStrings $EnumsAsStrings -ObjectDepthStatus $ObjectDepthStatus -ParentMemberType 0
                        }
                        $resultTable
                    } elseif ($InputObject -is [IEnumerable]) {
                        [Object[]]$resultArray = @(
                            foreach ($item in $InputObject) {
                                InnerConvert -InputObject $item -Depth $Depth -DepthCtr ($DepthCtr + 1) `
                                    -EnumsAsStrings $EnumsAsStrings -ObjectDepthStatus $ObjectDepthStatus -ParentMemberType 0
                            }
                        )
                        , $resultArray
                    } else {
                        # One must not test for [PSObject] because of some object properties are not of this type, e.g. RuntimeTypeHandle
                        try {
                            [OrderedHashTable]$resultTable = [ordered]@{}
                        } catch {
                            [HashTable]$resultTable = @{}
                        }
                        $ParentMemberType = $ParentMemberType -band -bnot [PSMemberTypes]::NoteProperty
                        [PSMemberTypes]$alwaysIncludedTypes = [PSMemberTypes]::Property -bor $ParentMemberType
                        foreach ($property in $InputObject.PSObject.Properties) {
                            if (-not $InputObject.Equals($property.Value)) {
                                if ($property.MemberType -band $alwaysIncludedTypes -or (
                                        $ObjectDepthStatus -lt 2 -and $property.Name -inotlike 'PS*'
                                    ) -or (
                                        $property.MemberType -band [PSMemberTypes]::NoteProperty -and (
                                            $null -eq $property.Value -or $property.Value.GetType().Name -in ('PSCustomObject', 'PSObject'))
                                    )
                                ) {
                                    $resultTable[$property.Name] = InnerConvert -InputObject $property.Value -Depth $Depth -DepthCtr ($DepthCtr + 1) `
                                        -EnumsAsStrings $EnumsAsStrings -ObjectDepthStatus 2 -ParentMemberType $property.MemberType
                                }
                            }
                        }
                        if ($resultTable.Count -or $InputObject -isnot [ValueType]) {
                            $resultTable
                        } else {
                            [String]$InputObject
                        }
                    }
                } else {
                    [String]$InputObject
                }
            }
        }
        if ($MyInvocation.ExpectingInput) {
            [Object]$completeInput = [ArrayList]::new()
        } else {
            [Object]$completeInput = $null
        }
    } process {
        if ($MyInvocation.ExpectingInput) {
            [void]$completeInput.Add($_)
        } else {
            $completeInput = $InputObject
        }
    } end {
        [Int32]$currentDepth = $Depth
        if ($MyInvocation.ExpectingInput -or $completeInput -is [Array]) {
            # don't enumerate HashTables and other IEnumerable
            if ($completeInput.Count -eq 0) {
                if (-not $NoEnumerate) {
                    $completeInput = $null
                }
            } elseif ($completeInput.Count -eq 1) {
                if ($NoEnumerate) {
                    if ($MyInvocation.ExpectingInput) {
                        $completeInput = $completeInput[0]
                    } else {
                        $currentDepth--
                    }
                } else {
                    $completeInput = $completeInput[0]
                    $currentDepth--
                }
            }
        }
        InnerConvert -InputObject $completeInput -Depth $currentDepth -DepthCtr 0 `
            -EnumsAsStrings $EnumsAsStrings -ObjectDepthStatus 0 -ParentMemberType 0
    }
}

Глубокое клонирование хэш-таблицы, за исключением того, что мы заменяем объекты PSObject на хеш-таблицы, когда находим их. В противном случае он ведет себя так же, как функция, на которой он основан.

      <#
.SYNOPSIS

Converts a PSObject to a hashtable by doing a deep clone
and converting PSObjects to Hashtables on the fly.

.NOTES

This function is based on Kevin Marquette's Get-DeepClone
function as documented below.
https://learn.microsoft.com/en-us/powershell/scripting/learn/deep-dives/everything-about-hashtable?view=powershell-7.3#deep-copies

.EXAMPLE

$Settings = [PSObject]@{
    foo = "foo"
    one = @{ two = "three" }
    four = [PSObject]@{ five = "six" }
    seven = @( @("eight", "nine") )
}

$Clone = Convert-PSObjectToHashtable $Settings
#>
function Convert-PSObjectToHashtable {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory,ValueFromPipeline)]
        [Object] $InputObject
    )

    process {
        $Clone = @{}
        switch ($InputObject.GetType().Name) {
            'PSCustomObject' {
                foreach ($Property in $InputObject.PSObject.Properties) {
                    $Clone[$Property.Name] = Convert-PSObjectToHashtable $Property.Value
                }
                return $Clone
            }
            'Hashtable' {
                foreach ($Key in $InputObject.Keys) {
                    $Clone[$Key] = Convert-PSObjectToHashtable $InputObject[$Key]
                }
                return $Clone
            }
            default { return $InputObject }
        }
    }
}

Для простого преобразования [PSCustomObject] в [Hashtable] ответ Кейта работает лучше всего.

Однако, если вам нужны дополнительные параметры, вы можете использовать

      
function ConvertTo-Hashtable {
    <#
    .Synopsis
        Converts an object to a hashtable
    .DESCRIPTION
        PowerShell v4 seems to have trouble casting some objects to Hashtable.
        This function is a workaround to convert PS Objects to [Hashtable]
    .LINK
        https://github.com/alainQtec/.files/blob/main/src/scripts/Converters/ConvertTo-Hashtable.ps1
    .NOTES
        Base ref: https://community.idera.com/database-tools/powershell/powertips/b/tips/posts/turning-objects-into-hash-tables-2
    #>
    PARAM(
        # The object to convert to a hashtable
        [Parameter(ValueFromPipeline = $true, Mandatory = $true)]
        $InputObject,

        # Forces the values to be strings and converts them by running them through Out-String
        [switch]$AsString,

        # If set, empty properties are Included
        [switch]$AllowNulls,

        # Make each hashtable to have it's own set of properties, otherwise,
        # (default) each InputObject is normalized to the properties on the first object in the pipeline
        [switch]$DontNormalize
    )
    BEGIN {
        $headers = @()
    }
    PROCESS {
        if (!$headers -or $DontNormalize) {
            $headers = $InputObject | Get-Member -type Properties | Select-Object -expand name
        }
        $OutputHash = @{}
        if ($AsString) {
            foreach ($col in $headers) {
                if ($AllowNulls -or ($InputObject.$col -is [bool] -or ($InputObject.$col))) {
                    $OutputHash.$col = $InputObject.$col | Out-String -Width 9999 | ForEach-Object { $_.Trim() }
                }
            }
        } else {
            foreach ($col in $headers) {
                if ($AllowNulls -or ($InputObject.$col -is [bool] -or ($InputObject.$col))) {
                    $OutputHash.$col = $InputObject.$col
                }
            }
        }
    }
    END {
        return $OutputHash
    }
}

Может быть, это излишество, но я надеюсь, что это поможет

Сегодня «самый простой способ» преобразовать PSCustomObject в Hashtable будет таким:

      $custom_obj | ConvertTo-HashtableFromPsCustomObject    

ИЛИ ЖЕ

      [hashtable]$custom_obj

И наоборот, вы можете преобразовать Hashtable в PSCustomObject, используя:

      [PSCustomObject]$hash_table

Единственная загвоздка в том, что эти отличные опции могут быть недоступны в более старых версиях PS.

Другие вопросы по тегам