How to add additional elements in an existing json file - json

This is what I am doing:
$appParametersXml = [Xml] (Get-Content "$appParameterFilePath\$appParameterFile")
$parameterJsonFile = "$appParameterFilePath\$applicationName"+ "." + $jsonFileName
# Transform the "Parameter" elements into a nested hashtable.
# Convert any values that can be interpreted as [int] to [int] and strip out any comments in the xml file.
$hash = [ordered] #{}
$appParametersXml.Application.Parameters.ChildNodes | Where-Object {$_.NodeType -ne 'Comment'} | % {
$hash[$_.Name] = #{ value = if ($num = $_.Value -as [int]) { $num } else { $_.Value }
}
}
# Wrap the hashtable in a top-level hashtable and convert to JSON.
[ordered] #{
'$schema' = 'https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#'
contentVersion ='1.0.0.0'
parameters = $hash
} | ConvertTo-Json |Out-File $parameterJsonFile
Write-Host "The JSON File is: " $parameterJsonFile
After I build the hash table with existing information from the XML file, I need to add additional parameter values like this Before converting to JSON
"parameters": {
"applicationName": {
"value": "somevalue"
},
"applicationTypeName": {
"value": "somevalue"
},
"applicationTypeVersion": {
"value": "somevalue"
},
Everything that I have tried so far has given me this as additional values. The regular XML values are being converted the correct way but the additional items that I am adding before converting are coming up like this!
"applicationName": "somevalue"
How can i seperate that out on different lines?

So, assuming your input xml file looks something like this ...
<application>
<parameters>
<applicationName>My Awesome App</applicationName>
<!--my awesome comment-->
<applicationTypeName>Mobile App</applicationTypeName>
<applicationTypeVersion>299</applicationTypeVersion>
<!--my other awesome comment-->
</parameters>
</application>
Here is my revised PowerShell ... you can't use if ($num = $_.Value -as [int]) casting as it won't work for 0, as it would be interpreted as false. I prefer to break the steps down and test and check each. Also I've used InnerText for the node value instead of Value as typically Value is evaluated as $null and I'm not sure what your xml looks like.
$fileXml = "./config.xml"
$fileJson = "./config.json"
$xmlContent = [Xml](Get-Content $fileXml)
# Transform the "Parameter" elements into a nested hashtable.
# Set any string values which are integers as [int] and strip out any comments in the xml file.
$parameters = [ordered]#{}
$nodes = $xmlContent.application.parameters.ChildNodes.Where{ $_.NodeType -ne 'Comment' }
foreach ($node in $nodes) {
$parameter = $node.Name
$value = $node.InnerText
if ($value -match "^\d+$") { $value = [int] $value }
$parameters.Add($parameter, #{ value = $value })
}
# if you need to add additional attributes, it's as simple as:
$parameters.Add("newParameter1", #{ value = "newValue1" })
$parameters.Add("newParameter2", #{ value = "newValue2" })
# Wrap the hashtable in a top-level hashtable and convert to JSON.
[ordered]#{
'$schema' = 'https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#'
contentVersion = '1.0.0.0'
parameters = $parameters
} | ConvertTo-Json | Out-File $fileJson
And here is the output saved to the json file:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"applicationName": {
"value": "My Awesome App"
},
"applicationTypeName": {
"value": "Mobile App"
},
"applicationTypeVersion": {
"value": 299
},
"newParameter1": {
"value": "newValue1"
},
"newParameter2": {
"value": "newValue2"
}
}
}

In case anyone else runs into this, it was as simple as doing this after the hash table gets created with the existing XML file
$appParametersFileName = "$appParameterFilePath\$appParameterFile"
$appParametersXml = [Xml] (Get-Content "$appParametersFileName")
$parameterJsonFile = "$appParameterFilePath\$applicationName"+ "." + $jsonFileName
# Transform the "Parameter" elements into a nested hashtable.
# Convert any values that can be interpreted as [int] to [int] and strip out any comments in the xml file.
$hash = [ordered] #{}
$appParametersXml.Application.Parameters.ChildNodes | Where-Object {$_.NodeType -ne 'Comment'} | % {
$hash[$_.Name] = #{ value = if ($num = $_.Value -as [int]) { $num } else { $_.Value }
}
}
$hash["newvalue1"]=#{ value="value1"}
$hash["newvalue2"]=#{ value="value2"}
# Wrap the hashtable in a top-level hashtable and convert to JSON.
[ordered] #{
'$schema' = 'https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#'
contentVersion ='1.0.0.0'
parameters = $hash
} | ConvertTo-Json |Out-File $parameterJsonFile
Write-Host "The JSON File is: " $parameterJsonFile

Related

How to convert json string into specified hashtable format with 5.1 powershell version?

I'm trying to convert json string into desired format of hashtable with powershell 5.1 version.
Used this code to convert json into hashtable but getting the o/p in the below mentioned format. How to convert this json into specified format of hashtable ?
My code :
function ConvertTo-Hashtable {
[CmdletBinding()]
[OutputType('hashtable')]
param (
[Parameter(ValueFromPipeline)]
$InputObject
)
process {
## Return null if the input is null. This can happen when calling the function
## recursively and a property is null
if ($null -eq $InputObject) {
return $null
}
## Check if the input is an array or collection. If so, we also need to convert
## those types into hash tables as well. This function will convert all child
## objects into hash tables (if applicable)
if ($InputObject -is [System.Collections.IEnumerable] -and $InputObject -isnot [string]) {
$collection = #(
foreach ($object in $InputObject) {
ConvertTo-Hashtable -InputObject $object
}
)
## Return the array but don't enumerate it because the object may be pretty complex
Write-Output -NoEnumerate $collection
} elseif ($InputObject -is [psobject]) { ## If the object has properties that need enumeration
## Convert it to its own hash table and return it
$hash = #{}
foreach ($property in $InputObject.PSObject.Properties) {
$hash[$property.Name] = ConvertTo-Hashtable -InputObject $property.Value
}
$hash
} else {
## If the object isn't an array, collection, or other object, it's already a hash table
## So just return it.
$InputObject
}
}
}
json :
$json = '[
{
"type": "IP",
"Fields": [
{
"Column": "FileHash",
"Id": "Address"
}
]
}
]'
Actual Format with my code:
Name Value
---- -----
Fields {System.Collections.Hashtable}
type IP
Desired Format :
Key : type
Value : IP
Name : type
Key : Fields
Value : {FileHash}
Name : Fields

Check if jObject exists in json File - Powershell

how can I check if the Object 'Production' in my json-File exists in Powershell?
an extract of my json File:
[
{
"UID": "x,
"Office": "xy",
"Production": "a"
}
]
In this case the key "Production" does exist in the json File. But that is not always the case.
How can I check if it exists via Powershell? With Get-Member?
my approach:
$json = Get-Content "C:\file.json" | ConvertFrom-Json
foreach ($item in $json) {
if (Get-Member -InputObject $item.Production) {Write-Host "Production exists"}
else {Write-Host "Production does not exist"}
}
THANKS!
You could check if the property exists by accessing the object's PSObject.Properties, one of the many ways to validate could be using the .Item(..) method:
$json = '[{ "Production": "Hello" }, { "OtherProp": "Nothing Here" }]' | ConvertFrom-Json
$count = 0
foreach($item in $json) {
$index = 'Object {0}' -f $count++
if($prop = $item.PSObject.Properties.Item('Production')) {
"{0}: Production Exists and it's value is: '{1}'" -f $index, $prop.Value
}
else {
"{0}: No property with name Production" -f $index
}
}
You can also use the property name to index to the NoteProperty Object with the following syntax:
if($prop = $item.PSObject.Properties['Production']) {
The first object from the example Json has a property with Name Production and Hello as Value while the second object does have the specified property. Above would result in:
Object 0: Production Exists and it's value is: 'Hello'
Object 1: No property with name Production

powershell - iterate over json keys that have similar name

I have a json block containing keys that have a similar name, each is numbered. I want to iterate over those keys. How can this be achieved?
Eg
$json = #"
{
"output": [
{
"AIeventCheck1": "A",
"AIeventCheck2": "B",
"AIeventCheck3": "C"
}
]
}
"#
$config = $json | ConvertFrom-Json
ForEach ($AIeventCheck in $config.output) {
Write-host AIeventCheck value: $AIeventCheck
}
target output:
A
B
C
Use the psobject memberset to access the individual properties of the object(s):
foreach($AIeventCheck in $config.output){
$AIEventCheckValues = $AIEventCheck.psobject.Properties |Where Name -like 'AIeventCheck*' |ForEach-Object Value
Write-Host AIeventCheck value: $AIeventCheckValues
}

How to expand environmental variables in JSON into valid JSON notation by PowerShell

This question relates to my other question and to this issue.
When I try to read a configuration from a JSON which contains an environmental variable, e.g. %USERPROFILE%\\source an obvious choice [System.Environment]::ExpandEnvironmentVariables($jsonString) expands the JSON string into non-valid JSON notation, e.g. "C:\Users\JohnDoe". The \U is not a valid JSON notation.
The question is how to overcome this problem (with some clean code).
If you are performing the replacement on the entire $jsonString then, yes,
$jsonString -replace '\\', '\\' will replace too many backslashes.
You could then beter do this instead:
$match = ([regex] '(%([^%]+)%)').Match($jsonString)
while ($match.Success) {
$search = $match.Groups[1].Value
$replace = [environment]::GetEnvironmentVariable($match.Groups[2].Value) -replace '\\', '\\'
$jsonString = $jsonString -replace $search, $replace
$match = $match.NextMatch()
}
This will only replace the matched %SomeVar% environment variables by what they expand into doubles all possible backslashes in that.
Test using some bogus JSON:
$jsonString = #"
{
"Name": "%USERNAME%",
"UserPath": "%USERPROFILE%\\source"
"WinDir": "%SystemRoot%"
"InetPath": "%SystemDrive%\\inetpub\\wwwroot",
}
"#
$match = ([regex] '(%([^%]+)%)').Match($jsonString)
while ($match.Success) {
$search = $match.Groups[1].Value
$replace = [environment]::GetEnvironmentVariable($match.Groups[2].Value) -replace '\\', '\\'
Write-Host $search
write-host $replace
$jsonString = $jsonString -replace $search, $replace
$match = $match.NextMatch()
}
$jsonString
Output:
{
"Name": "KUTlime",
"UserPath": "C:\\Users\\KUTlime\\source"
"WinDir": "C:\\WINDOWS"
"InetPath": "C:\\inetpub\\wwwroot",
}
In is general a bad practice to peek and poke into serialized object strings (as e.g. a Json and XML) using string methods like Replace. Instead it is better to de-serialize the string to an object, make your changes, and serialize it to a string again. This way you prevent pitfalls along with incorrectly replacing a backslash with a special meaning (such as an escape for a double quote or a Unicode character).
As it might be a hassle to recursively craw through a complex object to expand all the strings, I have written a small function for this:
function Expand-String {
[CmdletBinding()] param(
[Parameter(ValueFromPipeLine = $True)]$Object
)
Process {
if ($Object -is [string]) { $Object = [System.Environment]::ExpandEnvironmentVariables($Object) }
elseif ($Object -is [Collections.IDictionary]) {
Foreach ($Key in #($Object.get_Keys())) { $Object[$Key] = Expand-String $Object[$Key] }
}
elseif ($Object -is [Collections.IEnumerable]) {
for ($i = 0; $i -lt $Object.Count; $i++) { $Object[$i] = Expand-String $Object[$i] }
}
else {
Foreach ($Name in ($Object.PSObject.Properties | Where-Object { $_.MemberType -eq 'NoteProperty' }).Name) {
$Object.$Name = Expand-String $Object.$Name
}
}
$Object
}
}
$Json = #'
{
"Environment Variables": {
"Name": "%USERNAME%",
"UserPath": "%USERPROFILE%\\source",
"WinDir": "%SystemRoot%",
"InetPath": "%SystemDrive%\\inetpub\\wwwroot"
},
"Special Characters": {
"Quote": "Hello \"World\"",
"Unicode": ["\u1F60A", "\u1F44D"]
}
}
'#
$Object = $Json | ConvertFrom-Json | Expand-String
$Object.'Environment Variables'
Name UserPath WinDir InetPath
---- -------- ------ --------
KUTlime C:\Users\KUTlime\source C:\WINDOWS C:\inetpub\wwwroot
$Json = $Object | ConvertTo-Json -Depth 9
$Json
{
"Environment Variables": {
"Name": "KUTlime",
"UserPath": "C:\\Users\\KUTlime\\source",
"WinDir": "C:\\WINDOWS",
"InetPath": "C:\\inetpub\\wwwroot"
},
"Special Characters": {
"Quote": "Hello \"World\"",
"Unicode": [
"ὠA",
"ὄD"
]
}
}

Split Period-Delimited Nodes To JSON Object

I have many string entries (this are namespace/class trees) that look like the following:
appsystem
appsystem.applications
appsystem.applications.APPactivities
appsystem.applications.APPmanager
appsystem.applications.APPmodels
appsystem.applications.MAPmanager
appsystem.applications.MAPmanager.maphub
appsystem.applications.MAPmanager.mapmanager
appsystem.applications.pagealertsmanager
appsystem.authentication
appsystem.authentication.manager
appsystem.authentication.manager.encryptionmanager
appsystem.authentication.manager.sso
appsystem.authentication.manager.tokenmanager
But, I need the final output to be like:
{
"name": "appsystem",
"children": [
{
"name": "applications",
"children": [
{"name": "APPactivities"},
{"name": "APPmanager"},
{"name": "APPmodels"},
{"name": "MAPmanager",
"children": [
{"name": "maphub"},
{"name": "mapmanager"}
]},
{"name": "pagealertsmanager"}
]
},
{
"name": "authentication",
"children": [
{"name": "manager",
"children": [
{"name": "encryptionmanager"},
{"name": "sso"},
{"name": "tokenmanager"}
]}
]
}
]
}
The total nodes can be any number.
I am assuming I am going to need recursion but I am at a loss on where even to begin.
This builds up nested lists, PowerShell ConvertTo-JSON flattens the outer list.
You can change the $Line in $s to $line in (Get-Content input.txt).
But I think this does it:
$s = #'
appsystem
appsystem.applications
appsystem.applications.APPactivities
appsystem.applications.APPmanager
appsystem.applications.APPmodels
appsystem.applications.MAPmanager
appsystem.applications.MAPmanager.maphub
appsystem.applications.MAPmanager.mapmanager
appsystem.applications.pagealertsmanager
appsystem.authentication
appsystem.authentication.manager
appsystem.authentication.manager.encryptionmanager
appsystem.authentication.manager.sso
appsystem.authentication.manager.tokenmanager
'# -split "`r`n"
$TreeRoot = New-Object System.Collections.ArrayList
foreach ($Line in $s) {
$CurrentDepth = $TreeRoot
$RemainingChunks = $Line.Split('.')
while ($RemainingChunks)
{
# If there is a dictionary at this depth then use it, otherwise create one.
$Item = $CurrentDepth | Where-Object {$_.name -eq $RemainingChunks[0]}
if (-not $Item)
{
$Item = #{name=$RemainingChunks[0]}
$null = $CurrentDepth.Add($Item)
}
# If there will be child nodes, look for a 'children' node, or create one.
if ($RemainingChunks.Count -gt 1)
{
if (-not $Item.ContainsKey('children'))
{
$Item['children'] = New-Object System.Collections.ArrayList
}
$CurrentDepth = $Item['children']
}
$RemainingChunks = $RemainingChunks[1..$RemainingChunks.Count]
}
}
$TreeRoot | ConvertTo-Json -Depth 1000
Edit: It's too slow? I tried some random pausing profiling and found (not too surprisingly) that it's the inner nested loop, which searches children arrays for matching child nodes, which is being hit too many times.
This is a redesigned version which still builds the tree, and this time it also builds a TreeMap hashtable of shortcuts into the tree, to all the previously build nodes, so it can jump right too them instead of searching the children lists for them.
I made a testing file, some 20k random lines. Original code processed it in 108 seconds, this one does it in 1.5 seconds and the output matches.
$TreeRoot = New-Object System.Collections.ArrayList
$TreeMap = #{}
foreach ($line in (Get-Content d:\out.txt)) {
$_ = ".$line" # easier if the lines start with a dot
if ($TreeMap.ContainsKey($_)) # Skip duplicate lines
{
continue
}
# build a subtree from the right. a.b.c.d.e -> e then d->e then c->d->e
# keep going until base 'a.b' reduces to something already in the tree, connect new bit to that.
$LineSubTree = $null
$TreeConnectionPoint = $null
do {
$lastDotPos = $_.LastIndexOf('.')
$leaf = $_.Substring($lastDotPos + 1)
$_ = $_.Substring(0, $lastDotPos)
# push the leaf on top of the growing subtree
$LineSubTree = if ($LineSubTree) {
#{"name"=$leaf; "children"=([System.Collections.ArrayList]#($LineSubTree))}
} else {
#{"name"=$leaf}
}
$TreeMap["$_.$leaf"] = $LineSubTree
} while (!($TreeConnectionPoint = $TreeMap[$_]) -and $_)
# Now we have a branch built to connect in to the existing tree
# but is there somewhere to put it?
if ($TreeConnectionPoint)
{
if ($TreeConnectionPoint.ContainsKey('children'))
{
$null = $TreeConnectionPoint['children'].Add($LineSubTree)
} else {
$TreeConnectionPoint['children'] = [System.Collections.ArrayList]#($LineSubTree)
}
} else
{ # nowhere to put it, this is a new root level connection
$null = $TreeRoot.Add($LineSubTree)
}
}
$TreeRoot | ConvertTo-Json -Depth 100
(#mklement0's code takes 103 seconds and produces a wildly different output - 5.4M characters of JSON instead of 10.1M characters of JSON. [Edit: because my code allows multiple root nodes in a list which my test file has, and their code does not allow that])
Auto-generated PS help links from my codeblock (if available):
New-Object (in module Microsoft.PowerShell.Utility)
Get-Content (in module Microsoft.PowerShell.Management)
ConvertTo-Json (in module Microsoft.PowerShell.Utility)
To complement TessellatingHeckler's great answer with an alternative implementation that uses a recursive function.
The emphasis is on modularity and terseness, not performance.[1]
# Outer function that loops over all paths and builds up a one or more nested
# hashtables reflecting the path hierarchy, which are converted to JSON on output.
# Note that only a single JSON object is output if all paths share the same root
# component; otherwise, a JSON *array* is output.
function convert-PathsToNestedJsonObject([string[]] $paths) {
$hts = New-Object Collections.ArrayList
$paths.ForEach({
$rootName = $_.split('.')[0]
$ht = $hts.Where({ $_.name -eq $rootName }, 'First')[0]
if (-not $ht) { [void] $hts.Add(($ht = #{})) }
convert-PathToNestedHashtable $ht $_
})
$hts | ConvertTo-Json -Depth 100
}
# Recursive helper function that takes a path such as "appsystem.applications"
# and converts it into a nested hashtable with keys "name" and "children" to
# reflect the path hierarchy.
function convert-PathToNestedHashtable([hashtable] $ht, [string] $path) {
$name, $childName, $rest = $path -split '\.', 3
$ht.name = $name
if ($childName) {
if ($ht.children) {
$htChild = $ht.children.Where({ $_.name -eq $childName }, 'First')[0]
} else {
$ht.children = New-Object Collections.ArrayList
$htChild = $null
}
if (-not $htChild) {
[void] $ht.children.Add(($htChild = #{}))
}
convert-PathToNestedHashtable $htChild "$childName.$rest"
}
}
# Call the outer function with the input paths (assumed to be stored in $paths).
convert-PathsToNestedJsonObject $paths
[1] One deliberate type of optimization is applied, which, however, still keeps the code terse:
PSv4+ offers the (little-known) array methods .ForEach() and .Where(), which are not only noticeably faster than their cmdlet counterparts ForEach-Object and Where-Object, but also offer additional features.
Specifically:
$paths.ForEach({ ... }) is used instead of
$paths | ForEach-Object { ... }
$ht.children.Where({ $_.name -eq $childName }, 'First')[0] is used instead of
$ht.children | Where-Object { $_.name -eq $childName } | Select-Object -First 1