2

Say I have a hashtable like this:

$headers = @{
    'Content-Type'='application/json'
    'Authorization'= "Api-Key 123456789"
}

When I run that, I get the hashtable placed in the $headers variable.

My question is, is there a way, using one line of code) to get it write back out like this?

@{'Content-Type'='application/json'; 'Authorization'= "Api-Key 123456789" }

The closest I can come is something like this:

$headers.GetEnumerator() | ForEach-Object{"`"$($_.Name)`" = `"$($_.Value)`";"} 

But that has the output on two lines, and adds an extra ; and does not have the surrounding @{ }

Vaccano
  • 78,325
  • 149
  • 468
  • 850
  • 1
    Tempting to close this as a duplicate with [Save hash table in PowerShell object notation (PSON)](https://stackoverflow.com/q/15139552/1701026) – iRon Aug 30 '22 at 06:06
  • 1
    Makes sense, @iRon - I've marked it as a duplicate. While the linked post is about writing to a _file_, you need a _string_ representation first either way. – mklement0 Aug 30 '22 at 18:23

3 Answers3

2

Casting a type of [PSCustomObject] and stringifying it will give you those results:

"$([pscustomobject]$headers)"

returns:

@{Authorization=Api-Key 123456789; Content-Type=application/json}
Abraham Zinala
  • 4,267
  • 3
  • 9
  • 24
2

You lose quotes with Abraham's helpful answer. If you really need the quotes you could use something like this

($headers | ConvertTo-Json -Compress) -replace ',',';' -replace '^','@'

@{"Authorization":"Api-Key 123456789";"Content-Type":"application/json"}
Doug Maurer
  • 8,090
  • 3
  • 12
  • 13
  • 1
    I actually prefer this way of going about it. Guess you can also just do `"@$(($headers | ConvertTo-Json -Compress).Replace(',',';'))"` as well. – Abraham Zinala Aug 29 '22 at 23:07
2
  • Fundamentally, converting a hashtable instance to its equivalent source-code representation only works in simple cases, such as yours.

    • Converting is fundamentally limited to instance of those data types that (a) can be represented as literals and (b) whose values doesn't rely on being a specific instance of a .NET reference type.
  • A best-effort implementation is iRon's ConvertTo-Expression function:

    # Install the script.
    # You may be prompted to add the download directory to $env:PATH
    # If not, you can find the script's full path as follows:
    #   (Join-Path (Get-InstalledScript ConvertTo-Expression).InstalledLocation ConvertTo-Expression.ps1)
    Install-Script ConvertTo-Expression
    
    # ->
    #  "@{Authorization = 'Api-Key 123456789'; 'Content-Type' = 'application/json'}"
    @{
      'Content-Type'='application/json'
      'Authorization'= "Api-Key 123456789"
    } | ConvertTo-Expression -Expand 0
    
    • -Expand 0 requests a single-line representation; in iRon's own words:

[ConvertTo-Expression] is able serialize more complex objects than just hashtables; you are able to define to what level you want to expand the object using the -Expand parameter (which I think is a more flexible parameter than the usual all-or-nothing -Compress switch) [...] you might even compress this further using -Expand -1 which will also remove the unnecessary spaces.

  • Ideally, PowerShell itself would provide such a command, in the form of the inverse of the Import-PowerShellDataFile cmdlet.

    • GitHub issue #2875 suggests adding a complementary Export-PowerShellDataFile cmdlet, and the related GitHub issue #11300 also discusses in-memory versions to complement the file-based ones.

Here's an approximation of this functionality:

# Sample input hashtable.
$headers = [ordered] @{
  'Content-Type' = 'application/json'
  Authorization = "Api-Key 123456789"
  AnArray = 1, 'two',3, '3" of snow'
  ABool = $true
}

# Try to convert the above back into a source-code representation.
$headers.GetEnumerator() | 
  ForEach-Object `
    -Begin { $entries = [Collections.Generic.List[string]] @() } `
    -Process {
      $name = 
        if ($_.Name -match '\W') { "'{0}'" -f ($_.Name -replace "'", "''") }
        else                     { $_.Name }
      $values = 
        $_.Value | ForEach-Object {
          if ($_ -is [string]) { "'{0}'" -f ($_ -replace "'", "''") }
          elseif ($_ -is [bool]) { '$' + "$_".ToLower() }
          else { $_ } # !! More work is needed here for increased type fidelity.     
        }
      $entries.Add("$name = $($values -join ', ')")
    } `
    -End { '@{ ' + ($entries -join '; ') +  ' }' }

Output:

@{ 'Content-Type'='application/json'; Authorization='Api-Key 123456789'; AnArray=1, 'two', 3, '6'' tall'; ABool=$true }

Limitations:

  • Type-faithful representations:

    • Strings are single-quoted with appropriate escaping of embedded ' characters.

    • Booleans are recognized and represented as $true or $false.

  • Any other data type is represented unquoted:

    • This works with numeric types only...
    • ...and even there the specific input number type may be lost.
  • Only flat collections of values are supported, which are invariably represented as PowerShell array literals of type [object[]] (i.e., any specific collection type other than array and any strong array typing is lost), with each element represented according to the rules above.

mklement0
  • 382,024
  • 64
  • 607
  • 775