Search code examples
powershellobject

Powershell : child object edit alters original parent object


I have a PowerShell multidimensional object called $foo.

If I want to edit one specific child object, I can use :

$foo[15].Name = 'Something'

But I want to edit that child object without editing the parent object. If I use :

$bar = $foo[15]
$bar.Name = 'Something'

This still edits the $foo parent object.

Why are $foo and $bar linked ?

Why, when I edit one, does it edit the other ?

Is there anyway to bypass this, so I can edit the subobject without altering the original, parent object ?


Solution

  • Why, when I edit one, does it edit the other ?

    Because you're dealing with a reference type, when you do:

    $bar = $foo[15]
    

    Then $foo[15] and $bar have the same reference, you can tell by using [object]::ReferenceEquals($objA, $objB), for example:

    $foo = @(
        [pscustomobject]@{ Name = 'Something' }
        [pscustomobject]@{ Name = 'Something' }
    )
    
    $bar = $foo[1]
    [object]::ReferenceEquals($foo[1], $bar) # True
    

    Is there anyway to bypass this, so I can edit the sub object without altering the original, parent object ?

    Yes there is and the method really depends on the type itself and its depth or complexity, for instance for these simple pscustomobjects provided above, they have only one level deep, for them specifically we could use the .Copy() method, this would produce a shallow copy of the object but in this case it would suffice:

    $bar = $foo[1]
    [object]::ReferenceEquals($foo[1], $bar) # True
    $bar = $foo[1].PSObject.Copy()
    [object]::ReferenceEquals($foo[1], $bar) # False
    

    Now $bar can be safely updated without updating it's original reference. Other reference types may provide their own cloning implementation, i.e. Hash tables have their own .Clone() Method. For more complex objects or objects with greater depth, the way around this is via serialization and deserialization, the 2 most commonly used methods in PowerShell are Json and CliXml:

    $bar = $foo[1]
    [object]::ReferenceEquals($foo[1], $bar) # True
    
    # To and from Json
    $bar = $foo[1] | ConvertTo-Json -Depth 99 | ConvertFrom-Json
    [object]::ReferenceEquals($foo[1], $bar) # False
    
    # To and from CliXml
    $bar = [System.Management.Automation.PSSerializer]::Deserialize(
        [System.Management.Automation.PSSerializer]::Serialize($foo[1], 99))
    [object]::ReferenceEquals($foo[1], $bar) # False