Source/Public/Copy-ObjectGraph.ps1
<#
.SYNOPSIS Copy object graph .DESCRIPTION Recursively ("deep") copies a object graph. .PARAMETER InputObject The input object that will be recursively copied. .PARAMETER ListAs If supplied, lists will be converted to the given type (or type of the supplied object example). .PARAMETER DictionaryAs If supplied, dictionaries will be converted to the given type (or type of the supplied object example). This parameter also accepts the [`PSCustomObject`][1] types By default (if the [-DictionaryAs] parameters is omitted), [`Component`][2] objects will be converted to a [`PSCustomObject`][1] type. .PARAMETER ExcludeLeafs If supplied, only the structure (lists, dictionaries, [`PSCustomObject`][1] types and [`Component`][2] types will be copied. If omitted, each leaf will be shallow copied .Example # Deep copy a complete object graph into a new object graph $NewObjectGraph = Copy-ObjectGraph $ObjectGraph .Example # Copy (convert) an object graph using common PowerShell arrays and PSCustomObjects $PSObject = Copy-ObjectGraph $Object -ListAs [Array] -DictionaryAs PSCustomObject .Example # Convert a Json string to an object graph with (case insensitive) ordered dictionaries $PSObject = $Json | ConvertFrom-Json | Copy-ObjectGraph -DictionaryAs ([Ordered]@{}) .LINK [1]: https://learn.microsoft.com/dotnet/api/system.management.automation.pscustomobject "PSCustomObject Class" [2]: https://learn.microsoft.com/dotnet/api/system.componentmodel.component "Component Class" #> function Copy-ObjectGraph { [CmdletBinding(DefaultParameterSetName = 'ListAs')][OutputType([Object[]])] param( [Parameter(Mandatory=$true, ValueFromPipeLine = $True)] $InputObject, [Alias('ListsAs')]$ListAs, [Alias('DictionariesAs')]$DictionaryAs, [Switch]$ExcludeLeafs, [Alias('Depth')][int]$MaxDepth = 10 ) begin { function StopError($Exception, $Id = 'IncorrectArgument', $Group = [Management.Automation.ErrorCategory]::SyntaxError, $Object){ if ($Exception -is [System.Management.Automation.ErrorRecord]) { $Exception = $Exception.Exception } elseif ($Exception -isnot [Exception]) { $Exception = [ArgumentException]$Exception } $PSCmdlet.ThrowTerminatingError([System.Management.Automation.ErrorRecord]::new($Exception, $Id, $Group, $Object)) } $ListNode = if ($PSBoundParameters.ContainsKey('ListAs')) { if ($ListAs -is [String] -or $ListAs -is [Type]) { try { [PSNode](New-Object -Type $ListAs) } catch { StopError $_ } } else { [PSNode]$ListAs } } $DictionaryNode = if ($PSBoundParameters.ContainsKey('DictionaryAs')) { if ($DictionaryAs -is [String] -or $DictionaryAs -is [Type]) { try { [PSNode](New-Object -Type $DictionaryAs) } catch { StopError $_ } } else { [PSNode]$DictionaryAs } } $ListStructure = if ($ListNode) { $ListNode.Structure } $DictionaryStructure = if ($DictionaryNode) { $DictionaryNode.Structure } if (($ListStructure -eq 'Dictionary' -and $DictionaryStructure -ne 'Dictionary') -or ($DictionaryStructure -eq 'List' -and $ListStructure -ne 'List')) { $ListNode, $DictionaryNode = $DictionaryNode, $ListNode } if ($ListNode -and $ListNode.Structure -ne 'List') { StopError 'The -ListAs parameter requires a string, type or an object example that supports a list structure' } if ($DictionaryNode -and $DictionaryNode.Structure -ne 'Dictionary') { StopError 'The -DictionaryAs parameter requires a string, type or an object example that supports a dictionary structure' } function CopyObject([PSNode]$Node, [Type]$ListType, [Type]$DictionaryType) { if ($Node.Structure -eq 'Scalar') { if ($ExcludeLeafs -or $Null -eq $Node.Value) { return $Node.Value } else { $Node.Value.PSObject.Copy() } } elseif ($Node.Structure -eq 'List') { $Type = if ($Null -ne $ListType) { $ListType } else { $Node.Type } $Values = $Node.GetItemNodes().foreach{ CopyObject $_ -ListType $ListType -DictionaryType $DictionaryType } $Values = $Values -as $Type ,$Values } elseif ($Node.Structure -eq 'Dictionary') { # This will convert a dictionary to a PSCustomObject $Type = if ($Null -ne $DictionaryType) { $DictionaryType } else { $Node.Type } $IsDirectory = $Null -ne $Type.GetInterface('IDictionary') if ($IsDirectory) { $Dictionary = New-Object -Type $Type } else { $Dictionary = [Ordered]@{} } $Node.GetItemNodes().foreach{ $Dictionary[$_.Key] = CopyObject $_ -ListType $ListType -DictionaryType $DictionaryType } if ($IsDirectory) { $Dictionary } else { [PSCustomObject]$Dictionary } } } } process { $PSnode = [PSNode]::new($InputObject) $PSNode.MaxDepth = $MaxDepth CopyObject $PSNode -ListType $ListNode.Type -DictionaryType $DictionaryNode.Type } } |