I am using Powershell to edit a json-file. The indentation in the edited file looks weird and the lines are not in the order I want them to be.
I have used this code to edit my json-file:
$appSettings = Get-Content C:\test\appsettings.json | ConvertFrom-Json
$httpData = @{"Url"="http://*:2201"}
$httpsData = @{}
$httpsData.Add("Url","https://*:2203")
$httpsData.Add("SslProtocols",$("Tls12", "Tls13"))
$httpsData.Add("Certificate",@{"Subject"="SubjectName";"Store"="My";"Location"="CurrentUser";"AllowInvalid"="True";})
$endPointsData = @{}
$endPointsData.Add("Http",$httpData)
$endPointsData.Add("HttpsInlineCertStore",$httpsData)
$KestrelData = @{}
$KestrelData.Add("EndPoints",$endPointsData)
$appSettings | Add-Member -Name 'Kestrel' -Value $KestrelData -MemberType NoteProperty
$appSettings | ConvertTo-Json -Depth 6 | Out-File "C:\test\appsettings.json"
This is the result I want:
This is the result I get:
How to fix this? Thanks.
Problems:
The ordering issue is due to the fact that you are using hashtables (@{ ... }
), whose entries are inherently unordered.
[ordered] @{ ... }
), which preserve definition order.The pretty-printing behavior of ConvertTo-Json
in Windows PowerShell is unfortunate: 4 spaces per indentation level and 2 spaces between a property name and the (start of) its value, with nested content indented relative to the position of the opening delimiter ({
or [
), with the closing delimiter at the same indentation level as the opening one.
Additionally, even empty arrays and objects have multiline representations.
All in all, this makes for a needlessly wide and/or tall representations that make deep hierarchies hard to grasp.
Pretty-printing in PowerShell (Core) 7 is now more sensible and works the way you want: 2 spaces per indentation level, 1 space between property name and the (start of) its value, with nested content indented relative to the start of the enclosing property and the closing delimiter at the same indentation level as the property itself.
Empty arrays and objects now have inline representations (e.g. "Foo": []
)
Note that neither PowerShell edition offers ways to customize pretty-printing (only a way to disable it, with `-Compress).
Solutions:
Use the ConvertTo-AdvancedJson
cmdlet from the third-party PSAdvancedJsonCmdlet
module (install with Install-Module -Scope CurrentUser PSAdvancedJsonCmdlet
, for instance): it is essentially a backport of the better ConvertTo-Json
implementation found in PowerShell (Core) 7
If installing modules isn't an option, and short of writing your own JSON serializer, you can use a quick-and-dirty, suboptimal approximation of the desired pretty-printing, using a single, regex-based string substitution via the -replace
operator, as shown in the sample code below.
Short of writing your own JSON serializer, there is a string-substitution solution with stateful, line-by-line processing, wrapped in a Format-Json
custom function in this answer - you can use it instead of ConvertTo-Json
, because it has the latter built in.
-Indentation
parameter.The following is a self-contained example that:
Uses [ordered] @{ ... }
hashtables to ensure the desired ordering, via a single, nested ordered-hashtable literal.
Is cross-edition-compatible:
ConvertTo-AdvancedJson
if available, and falls back to the quick-and-dirty, suboptimal, regex-based string-replacement technique.
# Simulate your file input.
# As an aside: it's better to use -Raw with Get-Content in this case:
# Get-Content -Raw C:\test\appsettings.json | ConvertFrom-Json
$appSettings = [pscustomobject] @{ Foo = 'bar' }
# Construct the data to add.
$KestrelData = [ordered] @{
'Endpoints' = [ordered] @{
'Http' = [ordered] @{ 'Url' = 'http://*:2201' }
'HttpsInlineCertStore' = [ordered] @{
'Url' = 'https://*:2203'
'SslProtocols' = 'Tls12', 'Tls13'
'Certificate' = [ordered] @{
'Subject' = 'SubjectName'
'Store' = 'My'
'Location' = 'CurrentUser'
'AllowInvalid' = 'True'
}
}
}
}
# Add the data as a property to the preexisting JSON document.
$appSettings |
Add-Member -Name 'Kestrel' -Value $KestrelData -MemberType NoteProperty
# Serialize back to JSON (piping to Out-File omitted)
if ($PSVersionTable.PSVersion.Major -ge 7) {
# PowerShell (Core) 7 - no workaround needed.
$appSettings | ConvertTo-Json -Depth 6
}
elseif ((Get-Command -ErrorAction Ignore ConvertTo-AdvancedJson)) {
# Windows PowerShell: Use the PSAdvancedJsonCmdlet module, if available
$appSettings | ConvertTo-AdvancedJson -Depth 6
} else {
# Windows PowerShell: Imperfect workaround.
($appSettings | ConvertTo-Json -Depth 6) -replace '(?m)(?<=^ *) {4}', ' '
}
Output with the quick-and-dirty workaround:
{
"Foo": "bar",
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://*:2201"
},
"HttpsInlineCertStore": {
"Url": "https://*:2203",
"SslProtocols": [
"Tls12",
"Tls13"
],
"Certificate": {
"Subject": "SubjectName",
"Store": "My",
"Location": "CurrentUser",
"AllowInvalid": "True"
}
}
}
}
}
Output in PowerShell 7 or with ConvertTo-AdvancedJson
:
{
"Foo": "bar",
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://*:2201"
},
"HttpsInlineCertStore": {
"Url": "https://*:2203",
"SslProtocols": [
"Tls12",
"Tls13"
],
"Certificate": {
"Subject": "SubjectName",
"Store": "My",
"Location": "CurrentUser",
"AllowInvalid": "True"
}
}
}
}
}