183 lines
7.3 KiB
PowerShell
183 lines
7.3 KiB
PowerShell
function Write-OrderedJson {
|
|
<#
|
|
.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 InputPath
|
|
A path to a given file to reduce need to import file contents prior to running
|
|
|
|
.PARAMETER NoKeyReorder
|
|
[switch] Should avoid reordering keys
|
|
|
|
.PARAMETER Path
|
|
A filesystem path
|
|
#>
|
|
[CmdletBinding(DefaultParameterSetName = 'JsonObject')]
|
|
[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, ParameterSetName = 'JsonObject')]
|
|
$JsonObject,
|
|
[Parameter(Mandatory = $true, ParameterSetName = 'InputPath')]
|
|
$InputPath,
|
|
[switch]$NoKeyReorder,
|
|
[Parameter(Mandatory = $false)]
|
|
$Path
|
|
)
|
|
|
|
if ($PSCmdlet.ParameterSetName -eq 'InputPath') {
|
|
$JsonObject = (Get-Content -Path $InputPath -Raw)
|
|
}
|
|
|
|
if ($JsonObject -is [string]) {
|
|
$JsonObject = (ConvertFrom-Json $JsonObject)
|
|
} elseif ($JsonObject -is [Array]) {
|
|
$JsonObject = (ConvertFrom-Json ([string]::Join('',$JsonObject)))
|
|
}
|
|
|
|
#region innerFunctions
|
|
$stringBuilder = New-Object System.Text.StringBuilder
|
|
$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
|
|
}
|
|
|
|
$stringBuilder.Append($shortSpacesString) | Out-Null
|
|
|
|
if (![string]::IsNullOrWhiteSpace($JsonName)) {
|
|
$stringBuilder.Append("$quoteString$JsonName$quoteString : ") | Out-Null
|
|
}
|
|
$stringBuilder.AppendLine("[") | Out-Null
|
|
|
|
if (($JsonValue[0].GetType().Name -match $primitiveMatch) -or ($JsonValue[0] -is [string])) {
|
|
$JsonValue = $JsonValue | Sort-Object
|
|
}
|
|
|
|
foreach ($iter in $JsonValue) {
|
|
if ($iter.GetType().Name -match $primitiveMatch) {
|
|
# Write it without quotes
|
|
$stringBuilder.AppendLine("$spacesString$iter$commaString") | Out-Null
|
|
} elseif ($iter -is [string]) {
|
|
# Write it with quotes
|
|
$stringBuilder.AppendLine("$spacesString$quoteString$iter$quoteString$commaString") | Out-Null
|
|
} else {
|
|
# Must be a complex object, but without a name, so write the value
|
|
Build-JsonObject -JsonName $null -JsonValue $iter -Depth ($Depth + 1)
|
|
}
|
|
}
|
|
$stringBuilder.AppendLine("$shortSpacesString]$commaString") | Out-Null
|
|
}
|
|
|
|
function Build-JsonObject {
|
|
param (
|
|
$JsonName,
|
|
$JsonValue,
|
|
$Depth = 0
|
|
)
|
|
|
|
$spacesString, $shortSpacesString = (Get-JsonStringLeadsByDepth -Depth $Depth)
|
|
|
|
$stringBuilder.Append($shortSpacesString) | Out-Null
|
|
if (![string]::IsNullOrWhiteSpace($JsonName)) {
|
|
$stringBuilder.Append("$quoteString$JsonName$quoteString : ") | Out-Null
|
|
}
|
|
$stringBuilder.AppendLine("{") | Out-Null
|
|
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) {
|
|
$stringBuilder.AppendLine("$spacesString$quoteString$key$quoteString : null$commaString") | Out-Null
|
|
} else {
|
|
if ($iter -ceq "False") { $iter = $false }
|
|
if ($iter -ceq "True") { $iter = $true }
|
|
if ($iter.GetType().Name -match $primitiveMatch) {
|
|
$stringBuilder.AppendLine("$spacesString$quoteString$key$quoteString : $($iter.ToString().ToLower())$commaString") | Out-Null
|
|
} elseif ($iter -is [string]) {
|
|
$stringBuilder.AppendLine("$spacesString$quoteString$key$quoteString : $quoteString$iter$quoteString$commaString") | Out-Null
|
|
} 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)
|
|
}
|
|
}
|
|
}
|
|
$stringBuilder.AppendLine("$shortSpacesString}$commaString") | Out-Null
|
|
}
|
|
#endregion innerFunctions
|
|
|
|
#region primitives should just be written as-is
|
|
if ($JsonObject -is [string]) {
|
|
return "$quoteString$JsonObject$quoteString"
|
|
} elseif ($JsonObject -is [bool]) {
|
|
return $JsonObject.ToString().ToLower()
|
|
} elseif ($JsonObject.GetType().Name -match $primitiveMatch) {
|
|
return $JsonObject.ToString()
|
|
#endregion primitives should just be written as-is
|
|
} elseif ($JsonObject.GetType().IsArray) {
|
|
Build-JsonArrayNameValuePair -JsonName $null -JsonValue $JsonObject
|
|
} else {
|
|
Build-JsonObject -JsonName $null -JsonValue $JsonObject
|
|
}
|
|
|
|
# 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 = $stringBuilder.ToString()
|
|
$builtString = $regex.Replace($builtString, "`$1")
|
|
$builtString = $builtString.TrimEnd().TrimEnd(",")
|
|
|
|
if ([string]::IsNullOrWhiteSpace($Path)) {
|
|
Write-Host "Path: $path"
|
|
return $builtString
|
|
} else {
|
|
Set-Content -Path $Path -Value $builtString -Force | Out-Null
|
|
}
|
|
} |