【问题标题】:Proper formating of JSON using powershell使用 powershell 正确格式化 JSON
【发布时间】:2026-02-15 04:00:02
【问题描述】:

我有以下 JSON 格式的文件:

ConvertTo-JSON之前:

[
    {
        "Yura": {
            "Cashier": {
                "branch": "release/Retail-v4.0",
                "configuration": "RetailDemo Debug",
                "datetime_deployed": "Apr 18 2018 07:45:05",
                "deployed_by": "anonymous",
                "host": "cashier2-retail4.testing.aws.com",
                "job": "http://jenkins-testing.aws.com:8080/job/CashierDeployment",
                "lineserver": "",
                "messagebus": "",
                "product": "Cashier",
                "publish_profile": "cashier2.retail.dev.pubxml"
            },
            "ContentManager": {
                "branch": "release/Retail-v3.31.1",
                "configuration": "RetailDemo Debug",
                "datetime_deployed": "Jan 17 2018 11:59:24",
                "deployed_by": "anonymous",
                "host": "contentmanager2-retail3.testing.aws.com",
                "job": "http://jenkins-testing.aws.com:8080/job/ContentManagerDeployment",
                "lineserver": "",
                "messagebus": "",
                "product": "ContentManager",
                "publish_profile": "..\\ContentManager.PublishProfiles\\contentmanager2.retail5.dev.pubxml"
            }
        }
    }
]

使用此代码处理数据后:

$json = Get-Content 'D:\script\test.json'  -encoding utf8 | ConvertFrom-Json
$json.yura.ContentManager.branch = 'test'

我将 JSON 保存到另一个文件中:

$json | convertto-json | set-content "D:\script\test1.json" -encoding utf8

问题是,保存文件后,格式变坏了:

{
    "Yura":  {
                 "Cashier":  {
                                 "branch":  "release/Retail-v4.0",
                                 "configuration":  "RetailDemo Debug",
                                 "datetime_deployed":  "Apr 18 2018 07:45:05",
                                 "deployed_by":  "anonymous",
                                 "host":  "cashier2-retail4.testing.aws.com",
                                 "job":  "http://jenkins-testing.aws.com:8080/job/CashierDeployment",
                                 "lineserver":  "",
                                 "messagebus":  "",
                                 "product":  "Cashier",
                                 "publish_profile":  "cashier2.retail.dev.pubxml"
                             },
                 "ContentManager":  {
                                        "branch":  "test",
                                        "configuration":  "RetailDemo Debug",
                                        "datetime_deployed":  "Jan 17 2018 11:59:24",
                                        "deployed_by":  "anonymous",
                                        "host":  "contentmanager2-retail3.testing.aws.com",
                                        "job":  "http://jenkins-testing.aws.com:8080/job/ContentManagerDeployment",
                                        "lineserver":  "",
                                        "messagebus":  "",
                                        "product":  "ContentManager",
                                        "publish_profile":  "..\\ContentManager.PublishProfiles\\contentmanager2.retail5.dev.pubxml"
                                    }
             }
}

我的问题是 - 如何在 PowerShell 中保留源格式?

【问题讨论】:

    标签: json powershell formatting


    【解决方案1】:

    由于您的原始 json 包含一个只有一个元素的数组,PowerShell 会将其压缩为仅此一个元素。如果在您的输出中您希望它再次成为一个数组,请使用rokumaru's good answer

    但是,PowerShell 的 ConvertTo-Json 不会生成格式漂亮的 json,为此我前段时间编写了一个辅助函数:

    function Format-Json {
        <#
        .SYNOPSIS
            Prettifies JSON output.
        .DESCRIPTION
            Reformats a JSON string so the output looks better than what ConvertTo-Json outputs.
        .PARAMETER Json
            Required: [string] The JSON text to prettify.
        .PARAMETER Minify
            Optional: Returns the json string compressed.
        .PARAMETER Indentation
            Optional: The number of spaces (1..1024) to use for indentation. Defaults to 4.
        .PARAMETER AsArray
            Optional: If set, the output will be in the form of a string array, otherwise a single string is output.
        .EXAMPLE
            $json | ConvertTo-Json  | Format-Json -Indentation 2
        #>
        [CmdletBinding(DefaultParameterSetName = 'Prettify')]
        Param(
            [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
            [string]$Json,
    
            [Parameter(ParameterSetName = 'Minify')]
            [switch]$Minify,
    
            [Parameter(ParameterSetName = 'Prettify')]
            [ValidateRange(1, 1024)]
            [int]$Indentation = 4,
    
            [Parameter(ParameterSetName = 'Prettify')]
            [switch]$AsArray
        )
    
        if ($PSCmdlet.ParameterSetName -eq 'Minify') {
            return ($Json | ConvertFrom-Json) | ConvertTo-Json -Depth 100 -Compress
        }
    
        # If the input JSON text has been created with ConvertTo-Json -Compress
        # then we first need to reconvert it without compression
        if ($Json -notmatch '\r?\n') {
            $Json = ($Json | ConvertFrom-Json) | ConvertTo-Json -Depth 100
        }
    
        $indent = 0
        $regexUnlessQuoted = '(?=([^"]*"[^"]*")*[^"]*$)'
    
        $result = $Json -split '\r?\n' |
            ForEach-Object {
                # If the line contains a ] or } character, 
                # we need to decrement the indentation level unless it is inside quotes.
                if ($_ -match "[}\]]$regexUnlessQuoted") {
                    $indent = [Math]::Max($indent - $Indentation, 0)
                }
    
                # Replace all colon-space combinations by ": " unless it is inside quotes.
                $line = (' ' * $indent) + ($_.TrimStart() -replace ":\s+$regexUnlessQuoted", ': ')
    
                # If the line contains a [ or { character, 
                # we need to increment the indentation level unless it is inside quotes.
                if ($_ -match "[\{\[]$regexUnlessQuoted") {
                    $indent += $Indentation
                }
    
                $line
            }
    
        if ($AsArray) { return $result }
        return $result -Join [Environment]::NewLine
    }
    

    像这样使用它:

    $json = Get-Content 'D:\script\test.json' -Encoding UTF8 | ConvertFrom-Json
    $json.yura.ContentManager.branch = 'test'
    
    # recreate the object as array, and use the -Depth parameter (your json needs 3 minimum)
    ConvertTo-Json @($json) -Depth 3 | Format-Json | Set-Content "D:\script\test1.json" -Encoding UTF8
    
    # instead of using '@($json)' you can of course also recreate the array by adding the square brackets manually:
    # '[{0}{1}{0}]' -f [Environment]::NewLine, ($json | ConvertTo-Json -Depth 3) | 
    #        Format-Json | Set-Content "D:\script\test1.json" -Encoding UTF8
    

    【讨论】:

    • 这太棒了!谢谢你写这篇文章,@Theo。
    • 精彩的剧本!谢谢。
    【解决方案2】:

    如果整体是数组,元素是单个json文件,那就尴尬了。
    如果使用管道,则不会将其视为数组。

    $json | ConvertTo-Json -Depth 10 # bad
    

    因为它不是一个普通的数组,所以仅仅将它作为参数传递是行不通的。

    ConvertTo-Json $json -Depth 10  # bad
    

    如果你重新创建数组,效果会很好。

    ConvertTo-Json @($json) -Depth 10  # good
    

    【讨论】:

    • 是不是要让缩进和原文件一样?
    • 是的,我想保留源格式,就像原始文件(在 ConvertTo-JSON 之前)一样
    【解决方案3】:

    我写了下一个函数来修复缩进

    function FixJsonIndentation ($jsonOutput)
    {
        $currentIndent = 0
        $tabSize = 4
        $lines = $jsonOutput.Split([Environment]::NewLine)
        $newString = ""
        foreach ($line in $lines)
        {
            # skip empty line
            if ($line.Trim() -eq "") {
                continue
            }
    
            # if the line with ], or }, reduce indent
            if ($line -match "[\]\}]+\,?\s*$") {
                $currentIndent -= 1
            }
    
            # add the line with the right indent
            if ($newString -eq "") {
                $newString = $line
            } else {
                $spaces = ""
                $matchFirstChar = [regex]::Match($line, '[^\s]+')
                
                $totalSpaces = $currentIndent * $tabSize
                if ($totalSpaces -gt 0) {
                    $spaces = " " * $totalSpaces
                }
                
                $newString += [Environment]::NewLine + $spaces + $line.Substring($matchFirstChar.Index)
            }
    
            # if the line with { or [ increase indent
            if ($line -match "[\[\{]+\s*$") {
                $currentIndent += 1
            }
        }
    
        return $newString
    }
    

    【讨论】:

      【解决方案4】:

      更新了the answer from Theo,让它要求输入 JSON 格式,因为我只是想美化 JSON 数据。此外,删除了数学方法调用,因为我不能在我希望使用它的地方使用方法调用。

      非常感谢!

      function Format-Json {
          <#
          .SYNOPSIS
              Prettifies JSON output.
          .DESCRIPTION
              Reformats a JSON string so the output looks better than what ConvertTo-Json outputs.
          .PARAMETER Json
              Required: [string] The JSON text to prettify.
          .PARAMETER Minify
              Optional: Returns the json string compressed.
          .PARAMETER Indentation
              Optional: The number of spaces (1..1024) to use for indentation. Defaults to 4.
          .PARAMETER AsArray
              Optional: If set, the output will be in the form of a string array, otherwise a single string is output.
          .EXAMPLE
              $json | ConvertTo-Json  | Format-Json -Indentation 2
          #>
          [CmdletBinding(DefaultParameterSetName = 'Prettify')]
          Param(
              [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
              [string]$Json,
      
              [Parameter(ParameterSetName = 'Minify')]
              [switch]$Minify,
      
              [Parameter(ParameterSetName = 'Prettify')]
              [ValidateRange(1, 1024)]
              [int]$Indentation = 4,
      
              [Parameter(ParameterSetName = 'Prettify')]
              [switch]$AsArray
          )
      
          if ($PSCmdlet.ParameterSetName -eq 'Minify') {
              return ($Json | ConvertFrom-Json) | ConvertTo-Json -Depth 100 -Compress
          }
      
          # If the input JSON text has been created with ConvertTo-Json -Compress
          # then we first need to reconvert it without compression
          if ($Json -notmatch '\r?\n') {
              $Json = ($Json | ConvertFrom-Json) | ConvertTo-Json -Depth 100
          }
      
          $indent = 0
          $regexUnlessQuoted = '(?=([^"]*"[^"]*")*[^"]*$)'
      
          $result = $Json -split '\r?\n' |
              ForEach-Object {
                  # If the line contains a ] or } character, 
                  # we need to decrement the indentation level unless it is inside quotes.
                  if ($_ -match "[}\]]$regexUnlessQuoted") {
      
                      [int[]] $indentArray = ($indent - $Indentation),0
      
                      if ($indentArray[0] -gt $indentArray[1]) { 
                          $indent = $indentArray[0] 
                          }
                      else { 
                          $indent = 0
                          }
                  }
      
                  # Replace all colon-space combinations by ": " unless it is inside quotes.
                  $line = (' ' * $indent) + ($_.TrimStart() -replace ":\s+$regexUnlessQuoted", ': ')
      
                  # If the line contains a [ or { character, 
                  # we need to increment the indentation level unless it is inside quotes.
                  if ($_ -match "[\{\[]$regexUnlessQuoted") {
                      $indent += $Indentation
                  }
      
                  $line
              }
      
          if ($AsArray) { return $result }
          return $result -Join [Environment]::NewLine
      }
      
      
      $json = Read-Host "Enter json"
      
      $json | Format-Json
      
      

      【讨论】:

        【解决方案5】:

        如果您可以选择使用较新的PowerShell Core,格式现在已修复。 See this SO answer.

        【讨论】:

          【解决方案6】:

          我发现 Theo's excellent answer 导致空 [] 或 {} 或内联数组的缩进错误。

          示例输入:

          $JsonA = '{
              "Henk": "test",
              "Piet": [],
              "blub": {},
              "Joop": [
                  {
                      "a": "aaa",
                      "b": [],
                      "z": "lsjkal"
                  }
              ],
              "Klaas": [
                  [[],["a"],["b","c"]],
                  ["a","b"],
                  ["c"],
                  [],
                  "henk"
              ],
          }'
          
          $JsonA | Format-Json
          

          会导致:

          {
              "Henk": "test",
          "Piet": [],
          "blub": {},
              "Joop": [
                  {
                      "a": "aaa",
                  "b": [],
                      "z": "lsjkal"
                  }
              ],
              "Klaas": [
              [[],["a"],["b","c"]],
              ["a","b"],
              ["c"],
              [],
                  "henk"
              ],
          }
          

          编写了一个修复程序以确保当行同时包含 [/{ AND ]/} 时缩进正确:

          function Format-Json {
              <#
              .SYNOPSIS
                  Prettifies JSON output.
              .DESCRIPTION
                  Reformats a JSON string so the output looks better than what ConvertTo-Json outputs.
              .PARAMETER Json
                  Required: [string] The JSON text to prettify.
              .PARAMETER Minify
                  Optional: Returns the json string compressed.
              .PARAMETER Indentation
                  Optional: The number of spaces (1..1024) to use for indentation. Defaults to 4.
              .PARAMETER AsArray
                  Optional: If set, the output will be in the form of a string array, otherwise a single string is output.
              .EXAMPLE
                  $json | ConvertTo-Json  | Format-Json -Indentation 2
              #>
              [CmdletBinding(DefaultParameterSetName = 'Prettify')]
              Param(
                  [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
                  [string]$Json,
          
                  [Parameter(ParameterSetName = 'Minify')]
                  [switch]$Minify,
          
                  [Parameter(ParameterSetName = 'Prettify')]
                  [ValidateRange(1, 1024)]
                  [int]$Indentation = 4,
          
                  [Parameter(ParameterSetName = 'Prettify')]
                  [switch]$AsArray
              )
          
              if ($PSCmdlet.ParameterSetName -eq 'Minify') {
                  return ($Json | ConvertFrom-Json) | ConvertTo-Json -Depth 100 -Compress
              }
          
              # If the input JSON text has been created with ConvertTo-Json -Compress
              # then we first need to reconvert it without compression
              if ($Json -notmatch '\r?\n') {
                  $Json = ($Json | ConvertFrom-Json) | ConvertTo-Json -Depth 100
              }
          
              $indent = 0
              $regexUnlessQuoted = '(?=([^"]*"[^"]*")*[^"]*$)'
          
              $result = $Json -split '\r?\n' |
                  ForEach-Object {
                      # If the line contains a ] or } character, 
                      # we need to decrement the indentation level, unless:
                      #   - it is inside quotes, AND
                      #   - it does not contain a [ or {
                      if (($_ -match "[}\]]$regexUnlessQuoted") -and ($_ -notmatch "[\{\[]$regexUnlessQuoted")) {
                          $indent = [Math]::Max($indent - $Indentation, 0)
                      }
          
                      # Replace all colon-space combinations by ": " unless it is inside quotes.
                      $line = (' ' * $indent) + ($_.TrimStart() -replace ":\s+$regexUnlessQuoted", ': ')
          
                      # If the line contains a [ or { character, 
                      # we need to increment the indentation level, unless:
                      #   - it is inside quotes, AND
                      #   - it does not contain a ] or }
                      if (($_ -match "[\{\[]$regexUnlessQuoted") -and ($_ -notmatch "[}\]]$regexUnlessQuoted")) {
                          $indent += $Indentation
                      }
          
                      $line
                  }
          
              if ($AsArray) { return $result }
              return $result -Join [Environment]::NewLine
          }
          

          【讨论】: