-->

How to change tab width when converting to JSON in

2020-05-26 10:31发布

问题:

I am creating a JSON in Powershell and I want to set a custom tab width when building it (instead of the default 4 white spaces I want to set only 2 white spaces).

I am doing this because:

  • the actual JSON (not the one presented in the below sample) is pretty big (100k+ rows) and if not archieved, it's size is pretty big; If I reduce the tab width the size reduction is notable.
  • the actual JSON has a depth of 5+ nodes !
  • I cannot use -Compress since the JSON needs to be human readable
  • Yes, I agree, if archived, it's size is dramatically reduced but I need it also unarchived.

Sample code:

$object = New-Object PSObject
Add-Member -InputObject $object -MemberType NoteProperty -Name Phone -Value "SomePhone"
Add-Member -InputObject $object -MemberType NoteProperty -Name Description -Value "Lorem ipsum dolor.."
Add-Member -InputObject $object -MemberType NoteProperty -Name Price -Value 99.99

$object | ConvertTo-Json

Result with tab width = 4 white space characters.

{
    "Phone":  "SomePhone",
    "Description":  "Lorem ipsum dolor..",
    "Price":  99.99
}

I tried compression but it doesn't give control over compression level (how agressive compression should be)

$object | ConvertTo-Json -Compress

Result compressed, obviously.

{"Phone":"SomePhone","Description":"Lorem ipsum dolor..","Price":99.99}

What I am trying to achieve: result with tab width = 2 white space characters.

{
  "Phone":  "SomePhone",
  "Description":  "Lorem ipsum dolor..",
  "Price":  99.99
}

What I've tried so far is in the pseudo code below. I'm still in the loop. Please get me out of there :)

while (1) {
    Google, StackOverflow
    Try Stuff found 
    Tweak stuff found

    if (Correct answer) {
        break
    }
}

回答1:

The following code will halve the size of indent:

$json = @"
{
    "Phone":  "SomePhone",
    "Description":  "Lorem ipsum dolor..",
    "Price":  99.99
}
"@

($json -split '\r\n' |
% {
  $line = $_
  if ($_ -match '^ +') {
    $len  = $Matches[0].Length / 2
    $line = ' ' * $len + $line.TrimStart()
  }
  $line
}) -join "`r`n"


回答2:

Because the PowerShell's ConvertTo-Json produces non-deterministic indentation, the current answers will not produce JSON that has exactly two spaces for each depth in the data structure.

To get each level of nested data indented exactly two spaces more than the enclosing level requires rebuilding the indentation. (For what it's worth, looks like this was fixed in PowerShell 6)

After writing up my own solution, I found an almost identical one on GitHub, from Facebook's Daniel Lo Nigro (Daniel15) here. His is a PowerShell function that can take piped input. (I made the regex matches a bit more specific to reduce the likelihood of unintentional matching data.)

# Formats JSON in a nicer format than the built-in ConvertTo-Json does.
function Format-Json([Parameter(Mandatory, ValueFromPipeline)][String] $json) {
    $indent = 0;
    ($json -Split "`n" | % {
        if ($_ -match '[\}\]]\s*,?\s*$') {
            # This line ends with ] or }, decrement the indentation level
            $indent--
        }
        $line = ('  ' * $indent) + $($_.TrimStart() -replace '":  (["{[])', '": $1' -replace ':  ', ': ')
        if ($_ -match '[\{\[]\s*$') {
            # This line ends with [ or {, increment the indentation level
            $indent++
        }
        $line
    }) -Join "`n"
}

Usage: $foo | ConvertTo-Json | Format-Json



回答3:

Here is a simple method:

$data_json | convertto-json -depth 100 |
    foreach-object {$_ -replace "(?m)  (?<=^(?:  )*)", "`t" } |
    set-content 'output.json'

The foreach-object catches if you pass more than one object to ConvertTo-JSON.

Change the "`t" to what ever you want to do with the indent.



回答4:

You can use Newtonsoft.Json with PowerShell. There is a module for it in PowerShell Gallery that you can install to make it available in a convenient way.

Example:

if (!(Get-Module -ListAvailable -Name "newtonsoft.json")) {
    Install-Module -Name "newtonsoft.json" -Scope CurrentUser -Force
}

Import-Module "newtonsoft.json" -Scope Local

$JObject = [Newtonsoft.Json.Linq.JObject]::new(
    [Newtonsoft.Json.Linq.JProperty]::new("Phone", "SomePhone"),
    [Newtonsoft.Json.Linq.JProperty]::new("Description", "Lorem ipsum dolor.."),
    [Newtonsoft.Json.Linq.JProperty]::new("Price", 99.99));

$JObject.ToString()

Produces

{
  "Phone": "SomePhone",
  "Description": "Lorem ipsum dolor..",
  "Price": 99.99
}

It has tons of other features when working with json as well: https://www.newtonsoft.com/json/help/html/Introduction.htm