1

I need to sort properties by keys on all nesting levels. The problem occurs when I'm trying to reconstruct a structure of the original object. For some reason, $keyFull is treated as a key name, not a path. And I get output "main.nested": {} tho expected "main": { "nested": {} }. Where's my failure? Thanks!

Code

function sortAlphabeticallyRecursive( $hashtable )
{
    $sorted = [Ordered] @{ }

    function sortNested
    {
        param(
            [System.Object] $hashtable,
            [String] $keyFull
        )
        $hashtable.keys | ForEach-Object {
            if( $hashtable.$($_).Keys ) {
                if( $keyFull ) {
                    $keyFull = "$keyFull.$_"
                }
                else {
                    $keyFull = "$_"
                }
                if( ! $sorted.$($keyFull) ) {
                    $sorted.$($keyFull) = @{}
                }
                sortNested $hashtable.$($_) "$keyFull"
            }
            else {
                $hashtable.keys | ForEach-Object {
                    ( $hashtable.GetEnumerator() | sort key ) | ForEach-Object {
                        $sorted.($keyFull).($_.key) = $_.value
                    }
                }
            }
        }
    }

    sortNested( $hashtable, $null )
    return $sorted
}

$hashtable = @{ 'main' = @{
    'nested' = @{
        'aba' = '2';
        'aaa' = '1';
        'aca' = '3'
    }
} }

$sorted = sortAlphabeticallyRecursive( $hashtable )
$sorted | ConvertTo-Json -Depth 100

Output

{
    "main": {},
    "main.nested": {
        "aca": "3",
        "aba": "2",
        "aaa": "1"
    }
}

Expected

{
    "main": {
        "nested": {
            "aca": "3",
            "aba": "2",
            "aaa": "1"
        }
    }
}
MegaBomber
  • 345
  • 2
  • 3
  • 11
  • 1
    You are trying to access nested hashtable by path like `$hashtable."$keyFull.$_"`, which hashtables don't support. You would have to do `$hashtable.$keyFull.$_` instead. I think the code could be simplified so that you only work with key names and not paths. – zett42 Jul 15 '23 at 08:53
  • I will be very grateful if you show me how to do it! I'll accept your answer – MegaBomber Jul 15 '23 at 09:05
  • 1
    As an aside: PowerShell functions, cmdlets, scripts, and external programs must be invoked _like shell commands_ - `sortNested $hashtable $null` (no `(...)`, arguments separated with _spaces_) - _not_ like C# methods - `sortNested( $hashtable, $null )`. If you use `,` to separate arguments, you'll construct an _array_ that a command sees as a _single argument_. See [this answer](https://stackoverflow.com/a/65208621/45375) for details. – mklement0 Jul 15 '23 at 16:32

1 Answers1

4

You are trying to access nested hashtable by path like $hashtable."$keyFull.$_", which hashtables don't support. You would have to do $hashtable.$keyFull.$_ instead.

Though the code could be simplified so that you only work with key names and not paths:

function sortAlphabeticallyRecursive( [Collections.IDictionary] $hashtable ) {

    $ordered = [ordered] @{}

    $sortedKeys = $hashtable.Keys | Sort-Object

    foreach( $key in $sortedKeys ) {
        
        $value = $hashtable[ $key ]

        # If value is hashtable-like
        if( $value -is [Collections.IDictionary] ) {
            # recurse into nested hashtable
            $ordered[ $key ] = sortAlphabeticallyRecursive $value
        } else {
            # store single value
            $ordered[ $key ] = $value
        }
    }

    $ordered  # Output
}

$hashtable = @{ 'main' = @{
    'nested' = @{
        'aba' = '2';
        'aaa' = '1';
        'aca' = '3'
    }
} }

$sorted = sortAlphabeticallyRecursive $hashtable
$sorted | ConvertTo-Json -Depth 100

Output:

{
    "main":  {
        "nested":  {
            "aaa":  "1",
            "aba":  "2",
            "aca":  "3" 
        }
    }
}

Remarks:

  • The code uses the type Systems.Collections.IDictionary which is a common interface of all classes that support hashtable operations. This way the code is more flexible and supports all different kind of hashtable-like collections. In PowerShell the System namespace is always available, so we can shorten the type name to just Collections.IDictionary.
  • We are using IDictionary both to describe the kind of objects that the function accepts as argument and to test if each value is a nested collection using the -is operator.
zett42
  • 25,437
  • 3
  • 35
  • 72