简体   繁体   中英

Convert to JSON with comments from PowerShell

I have a very simple json and this code works for him:

function Get-CustomHeaders() {
   return Get-Content -Raw -Path $JsonName | ConvertFrom-Json
}

However, if my json has any comments // wololo it breaks. Would it be too hard to make this parser accept comments ?

在转换之前从您的输入中删除注释行:

(Get-Content $JsonName) -replace '^\s*//.*' | Out-String | ConvertFrom-Json

The solution in the other answer only removes // comments if they are at the beginning of a line (with or without spaces), and doesn't remove /* multiline comments */

This code removes all kind of // and /* multiline comments */ /

$configFile = (Get-Content path-to-jsonc-file -raw)
# Keep reading, for an improvement
# $configFile = $configFile -replace '(?m)\s*//.*?$' -replace '(?ms)/\*.*?\*/'

As @Jiří Herník indicates in his answer, this expression doesn't have into account the case of strings with comments inside it, for example "url": "http://mydomian.com" . To handle this case:

$configFile = $configFile -replace '(?m)(?<=^([^"]|"[^"]*")*)//.*' -replace '(?ms)/\*.*?\*/'

for example removing the comments in this file:

{
  // https://github.com/serilog/serilog-settings-configuration
  "Serilog": {
    "MinimumLevel": "Error", // Verbose, Debug, Information, Warning, Error or Fatal
    "WriteTo": [
      {
        "Name": "File",
        "Args": {
          "path": "D:\\temp\\MyService\\log.txt",
          "rollingInterval": "Day",
          "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] ({App}) ({Environment}) {Message:lj}{NewLine}{Exception}"
        }
      },
      {/*
        "Name": "Seq",*/
        "Args": {
          "serverUrl": "http://localhost:5341"
        }
      }
    ]
  }
}

results in:

{

  "Serilog": {
    "MinimumLevel": "Error",
    "WriteTo": [
      {
        "Name": "File",
        "Args": {
          "path": "D:\\temp\\MyService\\log.txt",
          "rollingInterval": "Day",
          "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] ({App}) ({Environment}) {Message:lj}{NewLine}{Exception}"
        }
      } ,
      {
        "Args": {
          "serverUrl": "http://localhost:5341"
        }
      }
    ]
  }
}

Here you have an example which can't be handled right by previous answers:

{
"url":"http://something" // note the double slash in URL
}

so here is regexp that solves also this problem.

$configFile = $configFile -replace '(?m)(?<=^([^"]|"[^"]*")*)//.*' -replace '(?ms)/\*.*?\*/'

IMPORTANT NOTE:

Powershell 6.0+ can load JSON with comments in it.

A simpler pattern that catches all combinations of string, escapes and comments is:

$configFile = $configFile -replace '("(\\.|[^\\"])*")|/\*[\S\s]*?\*/|//.*', '$1';

This assumes the file is valid, with no unclosed strings or comments. Invalid files are beyond the scope if this question.

The first part ("(\\\\.|[^\\\\"])*") matches full strings and skips any escaped characters, including \\\\ and \\" . This is captured so it can be placed back in the replacement string.

The second part /\\*[\\S\\s]*?\\*/ matches multiline comments. It uses [\\S\\s] instead of . , so linebreaks are also matched. It is a combination of non-whitespace characters ( \\S ) and whitespace characters ( \\s ). The *? is a lazy repetition, so it will prefer to match as little as possible, so it won't skip over any closing */ .

The last part //.* matches single line comments. The . won't match any linebreak, so it will only match until the end of the line.

When a string is matched, it is captured into slot 1. When a comment is matched, nothing is captured. The replacement is with whatever is in slot 1 ( $1 ). The result is that strings are matched but preserved, but comments are removed.

I wrote a function that takes any comments and puts them back into the JSON file if found.

This also allows reading and writing to the JSON file.

There are comments within. Tested in v5.1 and v7.

# Helper Function
# Write the contents of argument content to a file.
# Will create the file if it does not exist.
Function Write-ToFile {
  Param ([Parameter(Mandatory=$true, Position=0)] [string] $path,[Parameter(Mandatory=$true, Position=1)] [string] $content)
  [System.IO.File]::WriteAllText($path, $content)
}

Function Invoke-ReadWriteJSON {

  <#

  .SYNOPSIS
  Reads and writes properties from a JSON file.

  .DESCRIPTION
  This will allow JSON files to have comments, either multi-line or single line
  comments are supported.

  If the file does not exist or is empty then the default file contents are
  written to it.

  .NOTES
  Author: Ste
  Date Created: 2021.05.01
  Tested with PowerShell 5.1 and 7.1.
  Posted here: https://stackoverflow.com/questions/51066978/convert-to-json-with-comments-from-powershell

  .BUGS: NA

  .TODO: NA

  .PARAMETER filePath
  The file path of the JSON file.

  .PARAMETER Mode
  This parameter is either Read or Write.

  .PARAMETER Property
  The property of the JSON object.

  .PARAMETER newValue
  The new property of the JSON object.

  .INPUTS
  None. You cannot pipe objects to Add-Extension.

  .OUTPUTS
  Writes to or reads a file using the filePath parameter.

  .EXAMPLE (Write the property "Prop 1" with the value "Get in you machine!" to a file)
  PS> Invoke-ReadWriteJSON -filePath $jsonFilePath "Write" "Prop 1" "Get in you machine!"

  .EXAMPLE (Read a property from a file)
  PS> Invoke-ReadWriteJSON -filePath $jsonFilePath "Read" "Prop 2"
  PS> temp

  #>

  Param
  (
    [Parameter(Mandatory = $true, HelpMessage    = 'The file path of the JSON file.')]
    [String]$filePath,
    [Parameter(Mandatory = $true, HelpMessage    = 'This parameter is either Read or Write.')]
    [String]$Mode,
    [Parameter(Mandatory = $true, HelpMessage    = 'The property of the JSON object.')]
    [String]$Property,
    [Parameter(Mandatory = $false, HelpMessage   = 'The new property of the JSON object.')]
    [String]$newValue
    )

  # If there is a file then set its content else set the content variable to empty.
  if (Test-Path -LiteralPath $filePath) {
    $contents = Get-Content -LiteralPath $filePath
    $contents = $contents -replace '\s*' # Replace any whitespaces so that the length can be checked.
  }
  else {
    $contents = ''
  }

  # if the file does not exist or the contents are empty
  if ((Test-Path -LiteralPath $filePath) -eq $false -or $contents.length -eq 0) {
    Write-ToFile $filePath $jsonSettingFileDefaultContents
  }

  # This will allow single and multiline comments in the json file.
  # Regex for removing comments: https://stackoverflow.com/a/59264162/8262102
  $jsonContents = (Get-Content -LiteralPath $filePath -Raw) -replace '(?m)(?<=^([^"]|"[^"]*")*)//.*' -replace '(?ms)/\*.*?\*/' | Out-String | ConvertFrom-Json

  # Grab the comments that will be used late on.
  $jsonComments = (Get-Content -LiteralPath $filePath -Raw) -replace '(?s)\s*\{.*\}\s*'

  # Read the property.
  if ($Mode -eq "Read") {return $jsonContents.$Property}

  # Write the property.
  if ($Mode -eq "Write") {
    $jsonContents.$Property = $newValue
    $jsonContents | ConvertTo-Json -depth 32 | set-content $filePath
    # Trims any whitespace from the beginning and end of contents.
     Set-content $filePath ((Get-Content -LiteralPath $filePath -Raw) -replace '(?s)^\s*|\s*$')
  }

  # If there are comments then this section will add them back in. Important to
  # read contents with -Raw switch here.
  if ($jsonComments.length -gt 0) {
    $jsonNewcontents = (Get-Content -LiteralPath $filePath -Raw) -replace '(?m)(?<=^([^"]|"[^"]*")*)//.*' -replace '(?ms)/\*.*?\*/'
    # Trims any whitespace from the beginning and end of contents.
     Set-content $filePath (("$jsonComments`n" + $jsonNewcontents) -replace '(?s)^\s*|\s*$')
  }

}

$deskTopFolder = [Environment]::GetFolderPath("DesktopDirectory")
$jsonFilePath = "$deskTopFolder\color-dialog-settings.json"

$jsonSettingFileDefaultContents = @'
// Some comments go here.
// Some comments go here.
// Some comments go here.
{
  "Prop 1":  "temp",
  "Prop 2":  "temp"
}
'@

# Write the JSON property.
# Invoke-ReadWriteJSON -filePath $jsonFilePath "Write" "Prop 1" "Get in you machine!"

# Read the JSON property.
Invoke-ReadWriteJSON -filePath $jsonFilePath "Read" "Prop 2"
# PS> temp

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM