Search code examples
stringpowershellsplithashtabletext-parsing

How to get a hashtable in PowerShell from a multiline string in which keys and values are on different lines?


I have a string of the following format (the number of lines in this string may vary):

$content = @"
key1
value1
key2
value2
key3
value3
"@

I want to put this data in a hashtable.

(In my case, the data in the $content variable is received in the body of the HTTP response from the Invoke-WebRequest cmdlet by Client/Server Protocol in the 'LiveJournal'. But I am interested in the answer to my question for the general case as well.)

I tried to use the cmdlet ConvertFrom-StringData, but it doesn't work for this case:

PS C:\> ConvertFrom-StringData -StringData $content -Delimiter "`n"
ConvertFrom-StringData: Data line 'key1' is not in 'name=value' format.

I wrote the following function:

function toHash($str) {
  $arr = $str -split '\r?\n'
  $hash = @{}
  for ($i = 0; $i -le ($arr.Length - 1); $i += 2) {
    $hash[$arr[$i]] = $arr[$i + 1]
  }
  return $hash
}

This function works well:

PS C:\> toHash($content)

Name                           Value
----                           -----
key3                           value3
key2                           value2
key1                           value1

My question is: is it possible to do the same thing, but shorter or more elegant? Preferably in one-liner (see the definition of this term in the book 'PowerShell 101'). Maybe there is a convenient regular expression for this case?


Solution

  • As commented by @Santiago Squarzon;

    The code you already have looks elegant to me, shorter -ne more elegant

    For the "Preferably in one-liner", what exactly is definition of a one line:

    • A single line, meaning a text string with no linefeeds?
    • Or a single statement meaning a text string with no linefeeds and no semicolons?
      Knowing that there are several ways to cheat on this, like assigning a variable in a condition (which is hard to read).

    Anyways, a few side notes:
    The snippet you show might have a pitfall if you have an odd number of lines and Set-StrictMode -Version Lastest enabled:

    Set-StrictMode -Version Latest
    toHash "key1`nvalue1`nkey2"
    OperationStopped:
    Line |
       5 |      $hash[$arr[$i]] = $arr[$i + 1]
         |      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
         | Index was outside the bounds of the array.
    
    Name                           Value
    ----                           -----
    key1                           value1
    

    The variable neme $content, suggests that you reading the data from a file, possibly with Get-Connent. If that is indeed the case you might consider to stream the input (which conserves memory):

    $Content -split '\r?\n' | # Get-Content .\Data.txt
    Foreach-Object -Begin {
        $Hash = @{}
        $Key = $Null
    } -Process { 
        if (!$Key) {
            $Key = $_
        }
        else {
            $Hash[$Key] = $_
            $Key = $Null
        }
    } -End {
        $Hash
    }
    

    And if you create use an [ordered] dictionary, you might even put this is a single statement like:

    $Content -split '\r?\n' |Foreach-Object { $h = [Ordered]@{} } { if (!$h.count -or $h[-1]) { $h[$_] = $Null } else { $h[$h.Count - 1] = $_ } } { $h }
    

    (Note that -as with the propose in the question- I do not take into account that there might be empty lines in the input data)

    See also PowerShell issue: #13817 Enhance hash table syntax