title: Powershell: How to dynamically update a nested JSON array?
body: <p>What I'm trying to do is take any well-formed JSON file/object and search for a path inside it. If the path isn't found, move on. If it is found, update the value. Once updated, save the updated JSON to the original file.</p>
  
  <p>The catch to this, is the well-formed JSON structure is not known ahead of time. It's possible I might be searching hundreds of .json files on a disk, so the files that don't match any of my search terms can just be ignored.</p>
  
  <p>I'm struggling to wrap my head around how to solve this problem. Most of the examples out there don't have a JSON object with an array for one of the key values, or they don't access the properties dynamically when an array is involved.</p>
  
  <p>This link: <a href="https://stackoverflow.com/questions/45067317/powershell-how-to-update-replace-data-and-values-in-json-and-xml-object">Powershell: How to Update/Replace data and values in Json and XML Object</a> shows a (sort of)"real" JSON structure, but the accepted answer relies on knowing what the JSON structure is (the OP didn't ask for help with dynamic pathing).</p>
  
  <p>This link: <a href="https://stackoverflow.com/questions/46451994/set-value-of-nested-object-property-by-name-in-powershell?rq=1">Set Value of Nested Object Property by Name in PowerShell</a> has something very close, although when an array is in the mix, it doesn't work properly when setting.</p>
  
  <p>Here's some example JSON to use with this problem, though again, the structure is not known before the script runs. I'm looping over a list of files on disk, and executing for each file.</p>
  
  <pre class="lang-bsh prettyprint-override"><code>$JSON = ConvertFrom-Json '{
     "key1":"key 1 value",
     "options":{
        "outDir":"./app-dir",
        "lib":[
           "someLibrary",
           "anotherLibrary"
        ],
        "someObjects":[
           {
              "first":"I am first"
           },
           {
              "second":"I am second"
           }
        ]
     }
  }'
  </code></pre>
  
  <p>The string to search this json might look like the following:</p>
  
  <pre><code>$SearchString = 'options.someObjects.first'
  </code></pre>
  
  <p>Or perhaps, something non-existent like:</p>
  
  <pre><code>$SearchString = 'options.someObjects.foo'
  </code></pre>
  
  <p>Using the recursive function GetValue from the 2nd article works beautifully for getting (and much more elegant than what I was doing):</p>
  
  <pre><code>function GetValue($object, $key)
  {
      $p1,$p2 = $key.Split(".")
      if($p2) { return GetValue -object $object.$p1 -key $p2 }
      else { return $object.$p1 }
  }
  </code></pre>
  
  <p>However, the function SetValue does not work with an array. It returns an error stating "The property 'first' can not be found on this object."</p>
  
  <pre><code>function SetValue($object, $key, $Value)
  {
      $p1,$p2 = $key.Split(".")
      if($p2) { SetValue -object $object.$p1 -key $p2 -Value $Value }
      else { $object.$p1 = $Value }
  }
  </code></pre>
  
  <p>I am aware this is because $JSON.options.someObjects is an array, therefore to access the object with the "first" key, the path would be:</p>
  
  <pre><code>$JSON.options.someObjects[0].first
  </code></pre>
  
  <p>That's the problem I'm having. How do I dynamically iterate over all objects once it reaches a part of the path that needs iterating? That part of the path could be anywhere, or more levels down, etc...</p>
  
  <p>It's strange that powershell will allow you to dynamically iterate through an array when getting the value, but not when trying to set it.</p>
  
  <p>Here's a complete example which demonstrates the entire issue:</p>
  
  <pre><code>#Create JSON:
  $JSON = ConvertFrom-Json '{
      "key1":"key 1 value",
      "options":{
         "outDir":"./app-dir",
         "lib":[
            "someLibrary",
            "anotherLibrary"
         ],
         "someObjects":[
            {
               "first":"I am first"
            },
            {
               "second":"I am second"
            }
         ]
      }
  }'
  
  
  $SearchPath = 'options.someObjects.first'
  $NewValue = 'I am a new value'
  
  function GetValue($object, $key)
  {
      $p1,$p2 = $key.Split(".")
      if($p2) { GetValue -object $object.$p1 -key $p2 }
      else { return $object.$p1 }
  }
  
  function SetValue($object, $key, $Value)
  {
      $p1,$p2 = $key.Split(".")
      if($p2) { SetValue -object $object.$p1 -key $p2 -Value $Value }
      else { return $object.$p1 = $Value }
  }
  
  
  GetValue -object $JSON -key $SearchPath
  
  SetValue -object $JSON -key $SearchPath -Value $NewValue
  </code></pre>
  
  <p>I've been searching all kinds of different terms trying to arrive at a good solution for this problem, but so far, no luck. I'm fairly certain I'm not the 1st person to want to do this sort of thing, apologies if I missed the answer somewhere.</p>