$parent = @"
{
"Property1": "Property1Value",
"Description": "Generic Description",
"SubProperties": {
"SubSubTemplateProps": {
SubSubSubTemplateProps1 : "SubSubSubTemplateProps1",
SubSubSubTemplateProps2 : {
SubSubSubSubTemplateProps1 : [
{
"Key": "Name",
"Value": "Temp"
},
{
"Key": "SupSubSubProp2",
"Value": "Supprop2value"
}
]
}
}
}
}
"@ | ConvertFrom-Json
$templateobj = $parent.SubProperties.SubSubTemplateProps
$parent.SubProperties.psobject.Properties.Remove("SubSubTemplateProps")
for($j = 0; $j -lt 3; $j )
{
$i = "{0:D2}" -f ($j 1)
$letters = @("a","b","c")
foreach($letter in $letters)
{
$newobject = $templateobj
($newobject.SubSubSubTemplateProps2.SubSubSubSubTemplateProps1 | ? {$_.key -eq "Name"}).Value = "NewValue$letter$i"
$parent.SubProperties | Add-Member -MemberType NoteProperty -Name "SubSubProp$letter$i" -Value $newobject
}
}
$parent |convertto-json -Depth 100
Im expecting output like this:
{
"Property1": "Property1Value",
"Description": "Generic Description",
"SubProperties": {
"SubSubPropa01": {
"SubSubSubTemplateProps1": "SubSubSubTemplateProps1",
"SubSubSubTemplateProps2": {
"SubSubSubSubTemplateProps1": [
{
"Key": "Name",
"Value": "NewValuea01"
},
{
"Key": "SupSubSubProp2",
"Value": "Supprop2value"
}
]
}
},
"SubSubPropb01": {
"SubSubSubTemplateProps1": "SubSubSubTemplateProps1",
"SubSubSubTemplateProps2": {
"SubSubSubSubTemplateProps1": [
{
"Key": "Name",
"Value": "NewValueb01"
},
{
"Key": "SupSubSubProp2",
"Value": "Supprop2value"
}
]
}
},
"SubSubPropa02": {
"SubSubSubTemplateProps1": "SubSubSubTemplateProps1",
"SubSubSubTemplateProps2": {
"SubSubSubSubTemplateProps1": [
{
"Key": "Name",
"Value": "NewValuea02"
},
{
"Key": "SupSubSubProp2",
"Value": "Supprop2value"
}
]
}
},
"SubSubPropb02": {
"SubSubSubTemplateProps1": "SubSubSubTemplateProps1",
"SubSubSubTemplateProps2": {
"SubSubSubSubTemplateProps1": [
{
"Key": "Name",
"Value": "NewValueb02"
},
{
"Key": "SupSubSubProp2",
"Value": "Supprop2value"
}
]
}
}
}
}
But I get output like this where all of the noteproperties of the parent are updated instead of just the noteproperty I added. WHAT?!
{
"Property1": "Property1Value",
"Description": "Generic Description",
"SubProperties": {
"SubSubPropa01": {
"SubSubSubTemplateProps1": "SubSubSubTemplateProps1",
"SubSubSubTemplateProps2": {
"SubSubSubSubTemplateProps1": [
{
"Key": "Name",
"Value": "NewValueb02"
},
{
"Key": "SupSubSubProp2",
"Value": "Supprop2value"
}
]
}
},
"SubSubPropb01": {
"SubSubSubTemplateProps1": "SubSubSubTemplateProps1",
"SubSubSubTemplateProps2": {
"SubSubSubSubTemplateProps1": [
{
"Key": "Name",
"Value": "NewValueb02"
},
{
"Key": "SupSubSubProp2",
"Value": "Supprop2value"
}
]
}
},
"SubSubPropa02": {
"SubSubSubTemplateProps1": "SubSubSubTemplateProps1",
"SubSubSubTemplateProps2": {
"SubSubSubSubTemplateProps1": [
{
"Key": "Name",
"Value": "NewValueb02"
},
{
"Key": "SupSubSubProp2",
"Value": "Supprop2value"
}
]
}
},
"SubSubPropb02": {
"SubSubSubTemplateProps1": "SubSubSubTemplateProps1",
"SubSubSubTemplateProps2": {
"SubSubSubSubTemplateProps1": [
{
"Key": "Name",
"Value": "NewValueb02"
},
{
"Key": "SupSubSubProp2",
"Value": "Supprop2value"
}
]
}
}
}
}
Can someone suggest how to copy a node and replace a sub value then add it back to the parent without it overwriting all other child nodes? Not sure why this is happening.
CodePudding user response:
The fundamental problem, as explained in the comments and in more detail in this related answer is:
[pscustomobject]
instances, such as returned byConvertFrom-Json
are instances of a .NET reference type, which means that$newobject = $templateobj
doesn't create a copy of the object stored in$templateobj
in$newobject
, it creates a copy of the reference to object$templateobj
, so that$templateobj
and$newobject
end up pointing to the very same object. Only instances of .NET value types are themselves copied with simple assignments - see this answer for background information.
Therefore, you ended up repeatedly modifying the very same object instead of independent copies of it.While
$templateobj.psobject.Copy()
is a convenient method to create a shallow clone (copy) of a[pscustomobject]
instance, it isn't sufficient in your case, because your instances also contain reference-type instances among the object's property values, which then again get copies as references. In other words: your template object is a[pscustomobject]
graph (nested), so it requires deep-cloning, for which there is no built-in method.Note: If you know the structure of the template object ahead of time, there are simpler and more efficient solutions:
Use a literal
[pscustomobject]
object definition inside your loop, which creates a new instance every time - see the related answer.Construct your template as an ordered hashtable (
[ordered] @{ ... }
), and cast it to[pscustomobject]
every time you need a new copy inside the loop - see this answerDefine a custom
class
and instantiate it in every loop iteration - see this answer.
If you do need deep-cloning of [pscustomobject]
graphs, use helper function Copy-PSCustomObject
, defined further below, with the -Deep
switch:
$newobject = Copy-PSCustomObject -Deep $templateobj
Helper function Copy-PSCustomObject
:
Note: This is not a generic deep-cloning function that works with objects of any type (which would be impossible to implement), but it should work with [pscustomobject]
graphs such as returned by ConvertFrom-Json
, which limits their composition to:
Primitive JSON types that map onto primitive .NET types and
[string]
, all of which either are .NET value types or, in the case of[string]
act like them.Nested
[pscustomobject]
instances with the same composition.Arrays of either.
function Copy-PSCustomObject {
[CmdletBinding()]
param(
[Parameter(Mandatory, ValueFromPipeline)]
[PSCustomObject] $InputObject,
[switch] $Deep
)
begin {
if ($Deep) {
# Helper script block (anonymous function) for walking the object graph
$sb = {
param($original)
$copy = $original.psobject.Copy()
foreach ($prop in $copy.psobject.Properties) {
if ($prop.Value -is [System.Collections.IEnumerable]) { # Presumed array
$prop.Value = @($prop.Value) # Clone array
foreach ($i in 0..($prop.Value.Count-1)) {
$prop.Value[$i] = & $sb $prop.Value[$i] # Recurse
}
}
elseif ($prop.Value -is [System.Management.Automation.PSCustomObject]) {
$prop.Value = & $sb $prop.Value # Recurse
}
# Otherwise: assume it is a value type or string and needs no cloning.
}
# Output the deep-cloned object
$copy
}
}
}
process {
if ($Deep) {
& $sb $InputObject
}
else {
# Shallow copy.
$InputObject.psobject.Copy()
}
}
}