SOLVED

How to add a new set of key & values in json file.

Copper Contributor

Problem Statement: I want to add a set of new key & value in existing parsed Json after specific index. or position. For example: I have imported Json in $Json variable and then I wanted to add new sets of property right after property name 'Service1'. I able to make the script working out for me. However, not able to add new sets of key & value or property after specific position in file.

 

PowerShell Code:

function Get-EnvironmentManifest([string]$Filename) {    
        $Settings = Get-Content -Path $Filename -Encoding UTF8 | ConvertFrom-Json -ErrorAction STOP 
        return $Settings
    }
    
if (([System.Net.Dns]::GetHostByName($env:computerName)).HostName.Split('.')[0] -notmatch "UAT*") {
        $EnvironmentString = (([System.Net.Dns]::GetHostByName($env:computerName)).HostName.Split('.')[0].Split('-')[1]).ToUpper()
        for ($i = 1; $i -lt 5; $i++) {
            $ServiceName = 'Service' + [char](65 + $i)
            $HastTable = [ordered] @{"Name" = "Service1"; "ProfileType" = "Windows"; "ServiceName" = "$ServiceName";}            
            $Settings = Get-EnvironmentManifest -Filename $TargetJSON
            $Asset = New-Object -TypeName PSObject
            $Asset | Add-Member -NotePropertyMembers $HastTable -TypeName 'Asset'
            $Settings.Profiles.Services += $Asset
            $Settings | ConvertTo-Json -Depth 3| Set-Content -Path $TargetJSON        
        }    
    }

 

I Was Able to Create Following JSON Using Above PowerShell Code:

 

{
	"Environment": {},
	"Profiles": {
		"Services": [
			{
				"Name": "A"
			},
			{
				"Name": "B"
			},
			{
				"Name": "C"
			},
			{
				"Name": "D"
			},
			{
				"Name": "Service1",
				"ProfileType": "Windows",
				"ServiceName": "ServiceA"
			},
			{
				"Name": "E"
			},
			{
				"Name": "F"
			},
			{
				"Name": "Service1",
				"ProfileType": "Windows",
				"ServiceName": "ServiceB"
			},
			{
				"Name": "Service1",
				"ProfileType": "Windows",
				"ServiceName": "ServiceC"
			},
			{
				"Name": "Service1",
				"ProfileType": "Windows",
				"ServiceName": "ServiceD"
			},
			{
				"Name": "Service1",
				"ProfileType": "Windows",
				"ServiceName": "ServiceE"
			}
		]
	}
}

 

But I Want to Have Following Json:

 

{
	"Environment": {},
	"Profiles": {
		"Services": [
			{
				"Name": "A"
			},
			{
				"Name": "B"
			},
			{
				"Name": "C"
			},
			{
				"Name": "D"
			},
			{
				"Name": "Service1",
				"ProfileType": "Windows",
				"ServiceName": "ServiceA"
			},
			{
				"Name": "Service1",
				"ProfileType": "Windows",
				"ServiceName": "ServiceB"
			},
			{
				"Name": "Service1",
				"ProfileType": "Windows",
				"ServiceName": "ServiceC"
			},
			{
				"Name": "Service1",
				"ProfileType": "Windows",
				"ServiceName": "ServiceD"
			},
			{
				"Name": "Service1",
				"ProfileType": "Windows",
				"ServiceName": "ServiceE"
			},
			{
				"Name": "E"
			},
			{
				"Name": "F"
			}
		]
	}
}

 

3 Replies
best response confirmed by vivek10688 (Copper Contributor)
Solution

@vivek10688 

 

Can you provide us with an original example (i.e. before the script is called) of the file you're reading from on line 2?

 

There are a few things that don't make sense in the first output example when compared to how the script is written. Having the original example/JSON template will help.

 

In short though, you'd ideally want to work with the List type.

 

One thing you don't want to be doing is constantly reading from and writing to the file every iteration of the loop, as the current script does. That will scale very, very poorly as the object count goes up, so unless you're working with a very small data set, I'd be avoiding this approach.

 

Here's a somewhat abstract example - given the above caveats - using the List approach.

 

Bogus JSON template used

{
	"Environment": {},
	"Profiles": {
		"Services": [
			{
				"Name": "A"
			},
			{
				"Name": "B"
			},
			{
				"Name": "C"
			},
			{
				"Name": "D"
			},
			{
				"Name": "E"
			},
			{
				"Name": "F"
			}
		]
	}
}

 

Conceptual code only

$JsonObjects = Get-Content .\forum.json | ConvertFrom-Json;
$ServicesObjects = [System.Collections.Generic.List[PSCustomObject]]$Data.Profiles.Services;
$ServicesObjects.FindIndex({ param($Entry); $Entry.Name.Equals("E") });
$ServicesObjects.Insert($ServicesObjects.FindIndex({ param($Entry); $Entry.Name.Equals("E") }), ([PSCustomObject] @{ Name = "WhateverYouLike"; }));

 

This conceptual code is purely to demonstrate how to work with the List type. It is not a complete solution to your question.

 

Here's the output from this conceptual example.

LainRobertson_0-1658456052493.png

 

Once you've finished inserting items wherever you want them, you can then re-assign the List object back onto the broader JSON data set from where you obtained it and push that back out to JSON using the usual ConvertTo-Json commandlet, after which you can dump it back to file or do whatever you want to do.

 

$Data.Profiles.Services = $ServicesObjects;
$Data | ConvertTo-Json;

 

LainRobertson_1-1658456368571.png

 

Cheers,

Lain

HI @LainRobertson 

Thanks for providing the solution. I have updated your script for my use case and it worked as I wanted to have it. I will paste the working script at last. However, I am still confuse to understand a very simple snippet which is

$ServicesObjects.FindIndex({ param($Entry); $Entry.Name.Equals("E") });

 

Question: 

What does mean of ({ param($Entry); $Entry.Name.Equals("E") }); ?.

 

Working Script:

$Json = '
{
	"Environment": {},
	"Profiles": {
		"Services": [
			{
				"Name": "A"
			},
			{
				"Name": "B"
			},
			{
				"Name": "C"
			},
			{
				"Name": "D"
			},
			{
				"Name": "Service1",
				"ProfileType": "Windows",
				"ServiceName": "ServiceA"
			},
			{
				"Name": "E"
			},
			{
				"Name": "F"
			}			
		]
	}
}
'
function Format-Json {
    [CmdletBinding(DefaultParameterSetName = 'Prettify')]
    Param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [string]$Json,

        [Parameter(ParameterSetName = 'Minify')]
        [switch]$Minify,

        [Parameter(ParameterSetName = 'Prettify')]
        [ValidateRange(1, 1024)]
        [int]$Indentation = 4,

        [Parameter(ParameterSetName = 'Prettify')]
        [switch]$AsArray
    )

    if ($PSCmdlet.ParameterSetName -eq 'Minify') {
        return ($Json | ConvertFrom-Json) | ConvertTo-Json -Depth 100 -Compress
    }
    if ($Json -notmatch '\r?\n') {
        $Json = ($Json | ConvertFrom-Json) | ConvertTo-Json -Depth 100
    }

    $indent = 0
    $regexUnlessQuoted = '(?=([^"]*"[^"]*")*[^"]*$)'

    $result = $Json -split '\r?\n' |
        ForEach-Object {            
            if (($_ -match "[}\]]$regexUnlessQuoted") -and ($_ -notmatch "[\{\[]$regexUnlessQuoted")) {
                $indent = [Math]::Max($indent - $Indentation, 0)
            }
            $line = (' ' * $indent) + ($_.TrimStart() -replace ":\s+$regexUnlessQuoted", ': ')
            if (($_ -match "[\{\[]$regexUnlessQuoted") -and ($_ -notmatch "[}\]]$regexUnlessQuoted")) {
                $indent += $Indentation
            }
            $line
        }
    if ($AsArray) { return $result }
    return $result -Join [Environment]::NewLine
}

$JsonObjects = $Json | ConvertFrom-Json;
$ServicesObjects = [System.Collections.Generic.List[PSCustomObject]]$JsonObjects.Profiles.Services;
#$ServicesObjects.FindIndex({ param($Entry); $Entry.Name.Equals("E") });
for ($i = 1; $i -lt 5; $i++) {
    $ServiceName = 'Service' + [char](65 + $i)
	$HastTable = [PSCustomObject] [ordered] @{"Name" = "Service1"; "ProfileType" = "Windows"; "ServiceName" = "$ServiceName";}
    $ServicesObjects.Insert($ServicesObjects.FindIndex({ param($Entry); $Entry.Name.Equals("E") }), ($HastTable));
}
$JsonObjects.Profiles.Services = $ServicesObjects;
$JsonObjects | ConvertTo-Json -Depth 3 | Format-Json

 

Thanks

Vivek

@vivek10688 

 

Hey, Vivek.

 

That's an example of something called a "predicate".

 

The simplest explanation I can provide is that where you see a "predicate" as a parameter, instead of asking for a data type like "string", "int", "bool" etc., it's asking for a block of code, which is what you see between the curly braces (i.e. those braces indicate a "scriptblock".)

 

Here's some examples of predicates:

 

From PowerShell itself

LainRobertson_0-1658876127177.png

 

The corresponding Microsoft's .NET class documentation

 

So, that's the high-level answer. But what am I specifically doing with that line?

 

I'm looking for the position in the List "array" of the letter "E".

 

Breakdown:

 

param($Entry)

With a predicate, your code block is executed against each item within the List "array".

 

For each iteration, the current list item being checked is passed into your predicate as a parameter, and you receive - or "fetch" - this list item parameter using the "param()" definition.

 

So, this part is simple me fetching the list item as a parameter and assigning it to a variable I named $Entry. This allows me to do stuff later with the $Entry variable.

 

$Entry.Name.Equals("E")

There's probably nothing I need to explain here since it's a basic statement. The key thing is this is where I'm making use of the list item found in the $Entry parameter.

 

Since I'm working with your JSON as objects, "Name" is simply derived through being a property defined in your JSON template.

 

Some other basic examples of predicates

LainRobertson_1-1658876975729.png

 

Cheers,

Lain

1 best response

Accepted Solutions
best response confirmed by vivek10688 (Copper Contributor)
Solution

@vivek10688 

 

Can you provide us with an original example (i.e. before the script is called) of the file you're reading from on line 2?

 

There are a few things that don't make sense in the first output example when compared to how the script is written. Having the original example/JSON template will help.

 

In short though, you'd ideally want to work with the List type.

 

One thing you don't want to be doing is constantly reading from and writing to the file every iteration of the loop, as the current script does. That will scale very, very poorly as the object count goes up, so unless you're working with a very small data set, I'd be avoiding this approach.

 

Here's a somewhat abstract example - given the above caveats - using the List approach.

 

Bogus JSON template used

{
	"Environment": {},
	"Profiles": {
		"Services": [
			{
				"Name": "A"
			},
			{
				"Name": "B"
			},
			{
				"Name": "C"
			},
			{
				"Name": "D"
			},
			{
				"Name": "E"
			},
			{
				"Name": "F"
			}
		]
	}
}

 

Conceptual code only

$JsonObjects = Get-Content .\forum.json | ConvertFrom-Json;
$ServicesObjects = [System.Collections.Generic.List[PSCustomObject]]$Data.Profiles.Services;
$ServicesObjects.FindIndex({ param($Entry); $Entry.Name.Equals("E") });
$ServicesObjects.Insert($ServicesObjects.FindIndex({ param($Entry); $Entry.Name.Equals("E") }), ([PSCustomObject] @{ Name = "WhateverYouLike"; }));

 

This conceptual code is purely to demonstrate how to work with the List type. It is not a complete solution to your question.

 

Here's the output from this conceptual example.

LainRobertson_0-1658456052493.png

 

Once you've finished inserting items wherever you want them, you can then re-assign the List object back onto the broader JSON data set from where you obtained it and push that back out to JSON using the usual ConvertTo-Json commandlet, after which you can dump it back to file or do whatever you want to do.

 

$Data.Profiles.Services = $ServicesObjects;
$Data | ConvertTo-Json;

 

LainRobertson_1-1658456368571.png

 

Cheers,

Lain

View solution in original post