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.