user2363207
user2363207

Reputation:

PowerShell - Convert Property Names from Pascal Case to Upper Case With Underscores

Let's say I have an object like this:

$test = @{
          ThisIsTheFirstColumn = "ValueInFirstColumn"; 
          ThisIsTheSecondColumn = "ValueInSecondColumn"
         }     

and I want to end up with:

$test = @{
          THIS_IS_THE_FIRST_COLUMN = "ValueInFirstColumn"; 
          THIS_IS_THE_SECOND_COLUMN = "ValueInSecondColumn"
         } 

without manually coding the new column names.

This shows me the values I want:

$test.PsObject.Properties | where-object { $_.Name -eq "Keys" } | select -expand value | foreach{ ($_.substring(0,1).toupper() + $_.substring(1) -creplace '[^\p{Ll}\s]', '_$&').Trim("_").ToUpper()} | Out-Host

which results in:

THIS_IS_THE_FIRST_COLUMN
THIS_IS_THE_SECOND_COLUMN

but now I can't seem to figure out how to assign these new values back to the object.

Upvotes: 6

Views: 1645

Answers (3)

mklement0
mklement0

Reputation: 437618

You can modify hashtable $test in place as follows:

foreach($key in @($test.Keys)) { # !! @(...) is required - see below.
  $value = $test[$key] # save value
  $test.Remove($key)   # remove old entry
  # Recreate the entry with the transformed name.
  $test[($key -creplace '(?<!^)\p{Lu}', '_$&').ToUpper()] = $value
}

@($test.Keys) creates an array from the existing hashtable keys; @(...) ensures that the key collection is copied to a static array, because using the .Keys property directly in a loop that modifies the same hashtable would break.

The loop body saves the value for the input key at hand and then removes the entry under its old name.[1]

The entry is then recreated under its new key name using the desired name transformation:

$key -creplace '(?<!^)\p{Lu} matches every uppercase letter (\p{Lu}) in a given key, except at the start of the string ((?<!^)), and replaces it with _ followed by that letter (_$&); converting the result to uppercase (.ToUpper()) yields the desired name.


[1] Removing the old entry before adding the renamed one avoids problems with single-word names such as Simplest, whose transformed name, SIMPLEST, is considered the same name due to the case-insensitivity of hasthables in PowerShell. Thus, assigning a value to entry SIMPLEST while entry Simplest still exists actually targets the existing entry, and the subsequent $test.Remove($key) would then simply remove that entry, without having added a new one.
Tip of the hat to JosefZ for pointing out the problem.

Upvotes: 8

JosefZ
JosefZ

Reputation: 30113

I wonder if it is possible to do it in place on the original object?

($test.PsObject.Properties|Where-Object {$_.Name -eq "Keys"}).IsSettable says False. Hence, you need do it in two steps as follows:

$test = @{
          ThisIsTheFirstColumn = "ValueInFirstColumn"; 
          ThisIsTheSecondColumn = "ValueInSecondColumn"
         }
$auxarr = $test.PsObject.Properties |
  Where-Object { $_.Name -eq "Keys" } | 
    select -ExpandProperty value 
$auxarr | ForEach-Object { 
    $aux = ($_.substring(0,1).toupper() + 
    $_.substring(1) -creplace '[^\p{Ll}\s]', '_$&').Trim("_").ToUpper()
    $test.ADD( $aux, $test.$_)
    $test.Remove( $_)
}
$test

Two-step approach is necessary as an attempt to perform REMOVE and ADD methods in the only pipeline leads to the following error:

select : Collection was modified; enumeration operation may not execute.

Edit. Unfortunately, the above solution would fail in case of an one-word Pascal Case key, e.g. for Simplest = "ValueInSimplest". Here's the improved script:

$test = @{
          ThisIsTheFirstColumn = "ValueInFirstColumn"; 
          ThisIsTheSecondColumn = "ValueInSecondColumn"
          Simplest = "ValueInSimplest" # the simplest (one word) PascalCase
         }
$auxarr = $test.PsObject.Properties |
  Where-Object { $_.Name -eq "Keys" } | 
    select -ExpandProperty value
$auxarr | ForEach-Object { 
    $aux = ($_.substring(0,1).toupper() + 
      $_.substring(1) -creplace '[^\p{Ll}\s]', '_$&').Trim("_").ToUpper()
    $newvalue =  $test.$_
    $test.Remove( $_)
    $test.Add( $aux, $newvalue)
}
$test

Upvotes: 2

Mike Shepard
Mike Shepard

Reputation: 18156

This seems to work. I ended up putting stuff in a new hashtable, though.

$test = @{
          ThisIsTheFirstColumn = "ValueInFirstColumn"; 
          ThisIsTheSecondColumn = "ValueInSecondColumn"
         }  
$test2=@{}
$test.PsObject.Properties | 
    where-object { $_.Name -eq "Keys" } | 
    select -expand value | foreach{ $originalPropertyName=$_
                                    $prop=($_.substring(0,1).toupper() + $_.substring(1) -creplace '[^\p{Ll}\s]', '_$&').Trim("_").ToUpper()
                                    $test2.Add($prop,$test[$originalPropertyName])
                                  } 
$test2

Upvotes: 1

Related Questions