Replace a nested json object in a json array only if exists

HI,
I have a config.json file and within this file, I have an array of objects. in the deployment process I want Octopus to replace an existing Object in the array and if not exists to create it (see concept example):
original: { "ArrayOfObjects: [ { "ApiKey": "API KEY", "Server": "localhost", "Name": "The name" }, { "ApiKey": "SomeOtherApiKey", "Server": "Otherserver", "Name": "Should stay untouched" } ] }

Now, on deploy I want Octopus to check and replace/add:
if (ArrayOfObjects.Contains(obj => obj.Name == "The Name")) { replace (Server, "New Serve"); // can also be Replace(Object.Name == "The Name", // ( // { // ApiKey: "The key parameter from Octopus", // Server: "Server parameter from Octopus", // Name: "The Name" // The key parameter from Octopus // }); } else { Add ( { ApiKey: "The key parameter from Octopus", Server: "Server parameter from Octopus", Name: "The Name" }); }

Expected results:
{ "ArrayOfObjects: [ { "ApiKey": "API KEY", "Server": "New Server", "Name": "The name" }, { "ApiKey": "SomeOtherApiKey", "Server": "Otherserver", "Name": "Should stay untouched" } ] }
`

Is this doable? if so how would I go about it, I saw that in the Docs it say about the array that I can use the index, only I can not know it is there…
Thank you

Hi,

That’s an interesting question! I’ve had a look at this and come up with a solution. It isn’t a straight forward JSON variable substitution due to the requirement to “add an item if it doesn’t exist”.

The steps we’re trying to achieve:

  • For some APIs, do nothing with the JSON, keep the data as it is
  • For a number of Named APIs where we want to replace the key/server values
    • take the API name
    • see if the JSON object array has an item for that API already
    • if there is an item for that API then replace the ApiKey value and Server
    • if there is NO item for that API then create an entry and set the ApiKey and Server

To get this to work I took two steps

  1. Get all the items into the object array in the JSON file for each of the APIs
  2. Perform variable substitution to set the values

Pre-deployment Script

Within the deployment process step choose Configure Features and then select Custom Deployment Scripts

Documentation can be found here: https://octopus.com/docs/deployment-examples/custom-scripts

I used a Pre-deployment script.

The PowerShell script is as follows:

$extractPath = $OctopusParameters['Octopus.Action.Package.InstallationDirectoryPath'] 
$json = Get-Content "$extractPath\appsettings.json" | ConvertFrom-Json

$apiKeyNames = $OctopusParameters["OctoPetShop.ApiKeyItems"].split(',')

$jsonItemTemplate = "{'ApiKey': '###APIKEY###','Server': '###APIKEYSERVER###','Name': '###APIKEYNAME###'}" 

foreach($apiKeyName in $apiKeyNames){
    $foundItem = $json.ArrayOfObjects  | Where-Object { $_.Name -eq $apiKeyName }
    if ($foundItem) {
        $foundItem.ApiKey = "#{OctoPetShop.APIKey_$($apiKeyName)}"
        $foundItem.Server = "#{OctoPetShop.APIKeyServer_$($apiKeyName)}"

    } else {
        Write-Verbose "$($apiKeyName) NOT FOUND IN CONFIG, ADDING."
        $newJsonItem = $jsonItemTemplate.Replace("###APIKEY###","#{OctoPetShop.APIKey_$($apiKeyName)}").Replace("###APIKEYSERVER###","#{OctoPetShop.APIKeyServer_$($apiKeyName)}").Replace("###APIKEYNAME###","$($apiKeyName)")
        $json.ArrayOfObjects += ($newJsonItem | ConvertFrom-Json)
    }
}

$jsonString = ($json | ConvertTo-Json)
Set-Content -path "$extractPath\appsettings.json" -value $jsonString

Where the JSON I had in my file was

{
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*",
  "AppSettings": {
    "AppVersion": "0.0.0",
    "EnvironmentName": "Dev"
  },
  "ConnectionStrings": {
    "OPSConnectionString": "Server=(local)\\SqlExpress; Database=ops; Trusted_connection=true"
  },
  "ArrayOfObjects": [
    {
      "ApiKey": "API KEY",
      "Server": "localhost",
      "Name": "apiKey1"
    },
    {
      "ApiKey": "SomeOtherApiKey",
      "Server": "Otherserver",
      "Name": "Should stay untouched"
    }
  ]
}

To know which items I want in ArrayOfObjects I set a variable OctoPetShop.ApiKeyItems to be used by the script of OctoPetShop.ApiKeyItems and gave it a value of apiKey1,apiKey2,apiKey3,apiKey4

The script will take:

   {
      "ApiKey": "API KEY",
      "Server": "localhost",
      "Name": "apiKey1"
    },

and will output

   {
      "ApiKey": "#{OctoPetShop.APIKey_apiKey1}",
      "Server": "#{OctoPetShop.APIKeyServer_apiKey1}",
      "Name": "apiKey1"
    },

This gives us a file with a number of items that can have variable substitution applied to them.

Variable Substitution

Now we have all of the items required in the array we can look at putting the correct values against them.

Return to the Configure Features options on the step editor and then select Substitute variables in files in addition to Custom Deployment Scripts.

This now gives you a section on the step editor where you must supply the name of the file to perform variable substitution against, in this case appsettings.json:

Set the variables

The variables required to be set in order to get the replacements done are shown here:

Documentation on variables can be found here: https://octopus.com/docs/projects/variables
And on variable substitutions here: https://octopus.com/docs/projects/variables/variable-substitutions

Result

This gave the result shown here:

{
    "Logging": {
        "LogLevel": {
            "Default": "Warning"
        }
    },
    "AllowedHosts": "*",
    "AppSettings": {
        "AppVersion": "0.0.0",
        "EnvironmentName": "TheKeyForApi2"
    },
    "ConnectionStrings": {
        "OPSConnectionString": "Server=(local)\\SqlExpress; Database=ops; Trusted_connection=true"
    },
    "ArrayOfObjects": [
        {
            "ApiKey": "TheKeyForApi2",
            "Server": "https://server1/api",
            "Name": "apiKey1"
        },
        {
            "ApiKey": "SomeOtherApiKey",
            "Server": "Otherserver",
            "Name": "Should stay untouched"
        },
        {
            "ApiKey": "TheKeyForApi1",
            "Server": "https://server2/api",
            "Name": "ApiKey2"
        },
        {
            "ApiKey": "TheKeyForApi3",
            "Server": "https://server3/api",
            "Name": "ApiKey3"
        },
        {
            "ApiKey": "TheKeyForApi4",
            "Server": "https://server2/api",
            "Name": "ApiKey4"
        }
    ]
}

Hi, Lianne,
Thank you I will try this and report back :slight_smile:

Hi Lianne,
Thank you very much for the detailed answer, I got it to work as expected.Your explanation and example were perfect.
Thank you

Hi,

That’s excellent, thank you for letting me know!