Search code examples
jsonpowershellobject-graphpowershell-7.4

How to concisely add/update keys in JSON?


I'm using PowerShell scripts to amend JSON configuration files. Often, I have to add or update values of specific keys - that is, after I'm done, the key must be in the file with a specific value, but I do not tell (nor care) whether the key was in the file beforehand.

This is how I read my JSON files:

$settings = Get-Content -Raw "$settingsPath" | ConvertFrom-Json

Apparently (as per GetType()), the result of this is a PSCustomObject.

Now, to store value 42 in key foo, I can do one of two things:


A) Update the value:

$settings.foo = 42

But if foo does not exist, this will throw an exception saying property foo is unknown.


B) Create the value:

$settings | Add-Member -Name 'foo' -Type NoteProperty -Value 42

But if foo is already existing, this will throw an exception saying property foo is already there.


Now, of course I can check whether foo exists, and put that into a custom function1, but I feel like I'm missing something. Is there no built-in way to achieve this in a single call?


1: Note that the call required to check this, if ($settings.PSobject.Properties.name -match 'foo') {, is not the most straightforward of calls, either.


Solution

  • But if foo is already existing, this will throw an exception saying property foo is already there.

    That's what the -Force switch parameter is for - using it with Add-Member will suppress the collision error and overwrite the attached instance property anyway:

    $settings | Add-Member -Name 'foo' -Type NoteProperty -Value 42 -Force
    

    In PowerShell 7.x you also have the option of parsing the JSON into a hiearachy of ordered hashtables instead of custom objects:

    $settings = Get-Content -Raw "$settingsPath" | ConvertFrom-Json -AsHashTable
    

    Now you can update entry values with a simple index access operation:

    $settings['foo'] = "whatever"
    

    Note that the call required to check this, if ($settings.PSobject.Properties.name -match 'foo') {, is not the most straightforward of calls, either

    The Properties collection exposed by the psobject memberset actually provides a Match() method for exactly this, meaning you can simplify that sort of access slightly:

    if ($property = $settings.PSobject.Properties.Match('foo') |Select -First 1) {
      $property.Value = 42
    }