ps/Modules/Cole.PowerShell.Developer/Public/Write-OrderedJsonByArray.ps1

163 lines
6.1 KiB
PowerShell
Raw Permalink Normal View History

2023-05-30 22:51:22 -07:00
function Write-OrderedJsonByArray {
<#
.SYNOPSIS
Given a PSCustomObject, write it as pretty indented json WITH ORDERING for consistency
.PARAMETER JsonObject
An object that should be written as json. Should be a PSCustomObject or Hashtable
.PARAMETER NoKeyReorder
[switch] Should avoid reordering keys
.PARAMETER Path
A filesystem path
#>
[CmdletBinding()]
[OutputType([string])]
param (
# Setting to mandatory false because it can be null and that's ok, we just return null
[Parameter(Mandatory = $false, ValueFromPipeline = $true)]
$JsonObject,
[switch]$NoKeyReorder,
[Parameter(Mandatory = $false)]
$Path
)
#region innerFunctions
$script:fullText = @()
$quoteString = '"'
$commaString = ","
$OrderedKeys = !$NoKeyReorder
$primitiveMatch = 'byte|short|int32|long|sbyte|ushort|uint32|ulong|float|double|decimal|boolean'
function Get-JsonStringLeadsByDepth {
param (
$Depth = 0
)
$spacesString = [string]::new(" ", ($Depth + 1) * 2)
$shortSpacesString = ""
if ($Depth -gt 0) {
$shortSpacesString = [string]::new(" ", $Depth * 2)
}
return ($spacesString, $shortSpacesString)
}
function Build-JsonArrayNameValuePair {
param (
$JsonName,
$JsonValue,
$Depth = 0
)
$spacesString, $shortSpacesString = (Get-JsonStringLeadsByDepth -Depth $Depth)
if ($OrderedKeys) {
$JsonValue = $JsonValue | Sort-Object -Property Name,Key,Id
}
$header = ""
if (![string]::IsNullOrWhiteSpace($JsonName)) {
$header = "$quoteString$JsonName$quoteString : "
}
$header = "$shortSpacesString$header["
$script:fullText += $header
foreach ($iter in $JsonValue) {
if ($iter.GetType().Name -match $primitiveMatch) {
# Write it without quotes
$script:fullText += "$spacesString$iter$commaString"
} elseif ($iter -is [string]) {
# Write it with quotes
$script:fullText += "$spacesString$quoteString$iter$quoteString$commaString"
} else {
# Must be a complex object, but without a name, so write the value
Build-JsonObject -JsonName $null -JsonValue $iter -Depth ($Depth + 1)
}
}
$script:fullText += "$shortSpacesString]$commaString"
}
function Build-JsonObject {
param (
$JsonName,
$JsonValue,
$Depth = 0
)
$spacesString, $shortSpacesString = (Get-JsonStringLeadsByDepth -Depth $Depth)
$header = ""
if (![string]::IsNullOrWhiteSpace($JsonName)) {
$header = "$quoteString$JsonName$quoteString : "
}
$header = "$shortSpacesString$header{"
$script:fullText += $header
if (!(Test-IsCollectionNullOrEmpty $JsonValue.PSObject.Properties.Where({$_.MemberType -eq 'NoteProperty'}))) {
$JsonValue = $JsonValue.PSObject.Properties.Where({$_.MemberType -eq 'NoteProperty'})
}
if ($OrderedKeys) {
$JsonValue = $JsonValue | Sort-Object -Property Name, Key, Id
}
$keys = $JsonValue.Keys
if (Test-IsCollectionNullOrEmpty $keys) {
$keys = $JsonValue.Name
}
foreach ($key in $keys) {
$iter = $JsonValue[$key]
if ($null -eq $iter) {
$posit = $JsonValue.Where({$_.Name -eq $key})
if ($null -ne $posit) {
$iter = $posit.Value
}
}
if ($null -eq $iter) {
$script:fullText += "$spacesString$quoteString$key$quoteString : null$commaString"
} else {
if ($iter -ceq "False") { $iter = $false }
if ($iter -ceq "True") { $iter = $true }
if ($iter.GetType().Name -match $primitiveMatch) {
$script:fullText += "$spacesString$quoteString$key$quoteString : $($iter.ToString().ToLower())$commaString"
} elseif ($iter -is [string]) {
$script:fullText += "$spacesString$quoteString$key$quoteString : $quoteString$iter$quoteString$commaString"
} elseif ($iter.GetType().IsArray) {
# values are an array, so let's write the array values
Build-JsonArrayNameValuePair -JsonName $key -JsonValue $iter -Depth ($Depth + 1)
} else {
# It was not an array, or a primitive, so it must be _another_ object?
Build-JsonObject -JsonName $key -JsonValue $iter -Depth ($Depth + 1)
}
}
}
$script:fullText += "$shortSpacesString}$commaString"
}
#endregion innerFunctions
if ($JsonObject -is [string]) {
return $JsonObject
} elseif ($JsonObject -is [bool]) {
return $JsonObject.ToString()
} elseif ($JsonObject.GetType().Name -match $primitiveMatch) {
return $JsonObject.ToString()
} elseif ($JsonObject.GetType().IsArray) {
(Build-JsonArrayNameValuePair -JsonName $null -JsonValue $JsonObject -Depth 0)
} else {
(Build-JsonObject -JsonName $null -JsonValue $JsonObject -Depth 0)
}
# postprocess to convert this string: ",(\r?\n?\s*?[\]\}])" to this string "$1" (mind the escaping tho)
# That string says "any comma followed by any newline character(s) and any number of spaces, followed by a closing brace (indicating the end of an array or object) should remove the comma but retain the rest of the string"
# This removes trailing commas in arrays and object notations
$regex = New-Object System.Text.RegularExpressions.Regex(",(\r?\n?\s*?[\]\}])")
$builtString = [string]::Join([System.Environment]::NewLine,$script:fullText)
$builtString = $regex.Replace($builtString, "`$1")
$builtString = $builtString.TrimEnd().TrimEnd(",")
if ([string]::IsNullOrWhiteSpace($Path)) {
return $builtString
} else {
Set-Content -Path $Path -Value $builtString -Force | Out-Null
}
}