skip to Main Content

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:testappsettings.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:testappsettings.json"

This is the result I want:

How I want it to look

This is the result I get:

What it looks like now

How to fix this?
Thanks.

2

Answers


  1. This should do the trick.

    ($appSettings | ConvertTo-Json -Depth 6).Replace(' ',' ') | Out-File "C:testappsettings.json"

    Login or Signup to reply.
  2. Problems:

    • The ordering issue is due to the fact that you are using hashtables (@{ ... }), whose entries are inherently unordered.

      • Use ordered hashtables instead ([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 the 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+

      • For the sake of completeness: It follows that migrating to PowerShell (Core) 7+ would solve the problem too, but such a migration is certainly not something to undertake casually; on the plus side, PowerShell 7+ has fixed many problems that still plague Windows PowerShell, and continues to gain new features.
    • 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.

      • If you also want to use the function in PowerShell 7+, use this tweaked version, though note that the only good reason to do so would be if you wanted to indent by something other than 2 spaces, via the -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:

      • It detects running in PowerShell 7+, in which case no workaround is needed.
      • In Windows PowerShell, it uses ConvertTo-AdvancedJson if available, and falls back to the quick-and-dirty, suboptimal 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:testappsettings.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"
            }
          }
        }
      }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search