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 } }