1

I am a newbie to powershell, and I am trying to create function that copies an object from a json data, creates new object from it and assigns different values to the max parameter. So far, different versions of my implementation assigns only the last value from an array $ParameterValues to all the new objects created.

One solution, might be probabbly be to read the json data using a call by reference [ref]$jsonData. However, I am not even sure that is a thing in powershell.

--- Here is the sample json file:

"algorithms": {
    "obj0": {
      "command": "...",
      "parameters": {
        "min": 2.7,
        "max": 7
      }
    }

--- Function Select-Member helps select the object to copy

function Select-Member {
    [CmdLetBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$Path,
    
        [Parameter(Mandatory)]
        [Object]$InputParameter
    )
    Write-Debug $InputParameter
    $Path -split '/' | ForEach-Object { $selected = $InputParameter } { $selected = $selected.$_ } { $selected }
}

--- Function Set-Member pastes the new object copied using Select-Member or overwrites the values of the parameter selected

function Set-Member {
    [CmdletBinding()]
    param(  
        [Object]$Value,
        [string[]]$Path,
        [Object]$Object
    )

    $Head, $Next, $Tail = $Path
    if (($null -eq $Next) -or (1 -gt $Next.Length)) {
        Add-Member -Passthru -Force -MemberType NoteProperty `
            -Input $Object -Name $Head -Value $Value
    }
    else {
        Add-Member -Passthru -Force -MemberType NoteProperty `
            -Input $Object -Name $Head `
            -Value (Set-Member -Value $Value -Path ([string[]]$Next + $Tail) -Object ($Object.$Head))
    }
}

--- test-func function uses Select-Member to select an object (obj0 for example), then Set-Member adds new copies (obj1, obj2, obj3) of the object selected and then it should assign a new value to Max iteratatively from $ParameterValues array.

function test-func {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory, Position)]
        [string] $ObjectSelect,
        [Parameter(Mandatory, Position)]
        [string] $Parameter,
        [Parameter(Mandatory, Position)]
        [Object[]] $ParameterValues
    )
    $PathToPaste = ($ObjectSelect -Split '/', -2)[0] -Split '/'

    $JsonData = Get-content "$JSON_FILE" -raw | convertFrom-Json
    $ObjectSelected = Select-Member $ObjectSelect $JsonData

    [string]$NewObjName 
    foreach ($ele in $ParameterValues){
        $NewObjName = "obj" + $ele 
        Set-Member $ObjectSelected $NewObjName $JsonData.$PathToPaste
        $PathToParameter = $PathToPaste, $NewObjName, $Parameter-Split'/'
        Set-Member $ele $PathToParameter $JsonData
    }

    $JsonData | ConvertTo-Json -depth 32 | set-content "$JSON_FILE"
}

When I run the following command for example, test-func -ObjectSelect algorithms/obj0 -Parameter parameters/max -ParameterValues 1,2,3 to iteratatively assign each value from the $ParameterValues to max, it sets only the last value from the array to all the new objects created, obj1, obj2, obj3.

Here is the results I get. (Observe that the value max in the last three objects (obj1, obj2, obj3) are all identical = 3.

"algorithms": {
    "obj0": {
      "command": "...",
      "parameters": {
        "min": 2.7,
        "max": 7
      }
    },
    "obj1": {
      "command": "...",
      "parameters": {
        "min": 2.7,
        "max": 3
      }
    },
    "obj2": {
      "command": "...",
      "parameters": {
        "min": 2.7,
        "max": 3
      }
    },
    "obj3": {
      "command": "...",
      "parameters": {
        "min": 2.7,
        "max": 3
      }
    }
  }

The expected results should be like this (Observe that the value of max are 1, 2, 3 for the last three objects (obj1, obj2, obj3) respectively.):

"algorithms": {
    "obj0": {
      "command": "...",
      "parameters": {
        "min": 2.7,
        "max": 7
      }
    },
    "obj1": {
      "command": "...",
      "parameters": {
        "min": 2.7,
        "max": 1
      }
    },
    "obj2": {
      "command": "...",
      "parameters": {
        "min": 2.7,
        "max": 2
      }
    },
    "obj3": {
      "command": "...",
      "parameters": {
        "min": 2.7,
        "max": 3
      }
    }
  }

As of now, the only solution I have is using the following for-each loop in the test-func` function, which is definitely not professional. Notice it saves and reads again the json file on line 4 and 5. Like I said I am sure this is not professional as it will take up memory and time.

foreach ($ele in $ParameterValues){ $NewObjName = "obj" + $ele Set-Member $ObjectSelected $NewObjName $JsonData.$PathToPaste $JsonData | ConvertTo-Json -depth 32 | set-content "$JSON_FILE" $JsonData = Get-content "$JSON_FILE" -raw | convertFrom-Json $PathToParameter = $PathToPaste, $NewObjName, $Parameter-Split'/' Set-Member $ele $PathToParameter $JsonData }

Tchouba55
  • 13
  • 3
  • I didn't test your script (as I don' t see a difference between actual and expected) but I suspect it is related to [not all properties displayed](https://stackoverflow.com/a/44429084/1701026) as your `$head` property name is probably not the same for all objects. – iRon Apr 17 '21 at 07:30
  • Hello @iRon if you look at the max parameter, the value I get for `obj1`, `obj2`, and `obj3` are all identical ie equal to 3, the last value passed to the `parameterValues` array. – Tchouba55 Apr 17 '21 at 12:05
  • Need to give sample json data otherwise your code is useless. – Doug Maurer Apr 18 '21 at 22:18
  • Hello @DougMaurer, thanks for your reply I have made some corrections and added the sample json file I have initially. – Tchouba55 Apr 19 '21 at 13:29
  • The other important part that is missing is select-member. You really, really should provide a [MRE](https://stackoverflow.com/help/minimal-reproducible-example). – Doug Maurer Apr 19 '21 at 13:35
  • Hello @DougMaurer, I think the MRE is complete. Thanks. – Tchouba55 Apr 19 '21 at 18:45
  • The output I see when running your example command is not that just the max is set to 3, but instead each obj has 3 occurrences. `$a.obj2.parameters min max --- --- 2.7 3 2.7 3 2.7 3` – Doug Maurer Apr 19 '21 at 21:36
  • `$a.obj1 command parameters ------- ---------- abc @{min=2.7; max=3} abc @{min=2.7; max=3} abc @{min=2.7; max=3}` – Doug Maurer Apr 19 '21 at 21:37
  • Remove `| set-content "$JSON_FILE"` from your test func and look at the output to see if it's what you're expecting (besides the max value) – Doug Maurer Apr 19 '21 at 22:24

1 Answers1

0

The main issue here is that in the loop, you are still referencing the initial object. So the last $ele passed will change the values of all new objects created in that way.

You to use psobject.copy() to create a copy of the new object. But there is a catch, this only creates a shallow copy, that is if you have nested objects (where the properties contain other objects), only the top-level values are copied. The child objects will reference each other.

Since you are trying to update the value of one of the parameters. You will have to introduce something like this in the for-loop.

$ThisObject = $JsonData.($TargetPath[...]).psObject.Copy()
$ThisObject.$TargetProperty =  $ParameterValues[$i]
$JsonData.($TargetPath[...])| Add-Member -Name "obj$($i+1)" -Value
         $ThisObject -MemberType NoteProperty

Another solution will be to create a deep copy, especially if you have multiple updates to make at different depths. For more on that, you can visit this link

Skovzki
  • 16
  • 1
  • Thanks alot . I evetually implemented an algorithm with the piece of code you shared. Earlier I did use a copy but the results werenät what I expected, I missed the part where it mentioned shallow copy. I will test using a deepcopy later. Thanks – Tchouba55 Apr 29 '21 at 11:02