perl

Easy way to replace all keys in a data structure with a standard one


I have a data structure like this one (with many hashes & arrays, containing different environments, users, profiles, access rights etc.) :

%AR = (
'prod' => {
                   'username' => {
                                        'access_profile1' => [
                                                             'accessright1',
                                                             'accessright2',
                                                             'accessright3',
                                                             'accessrightN',
                                                            ],
                                        'access_profile2' => [
                                                             'accessright7',
                                                             'accessright8',
                                                             'accessright9',
                                                            ],
                                        'access_profileN' => [
                                                             'accessrightX',
                                                             'accessrightX',
                                                             'accessrightX',
                                                            ],
                                       },
                   },
);

I would like to give my script an option that, when enabled, it must force all profile names with a standard one (e.g. default_profile) in the structure (regenerate it) - of course this would mean that all accessrights are unique otherwise some might override others -

I know that I could do it by simply looping over this structure and filling a "new" structure, like this:

my $newAR;

foreach my $env ( keys %AR ) {
   foreach my $user ( keys %{ $AR{$env} } ) {
      foreach my $profile ( keys %{ $AR{$env}{$user} } ) {

         # fill the new data structure
         foreach my $accessright (@{ $AR{$env}{$user}{$profile} }) {
            push (@{ $newAR{$env}{$user}{'default_profile'} }, $accessright);
         }

      }
   }
}

which at the end gives the correct result:

$VAR1 = {
          'prod' => {
                      'username' => {
                                      'default_profile' => [
                                                             'accessrightX',
                                                             'accessrightX',
                                                             'accessrightX',
                                                             'accessright7',
                                                             'accessright8',
                                                             'accessright9',
                                                             'accessright1',
                                                             'accessright2',
                                                             'accessright3',
                                                             'accessrightN'
                                                           ]
                                    }
                    }
        };

Isn't there a "cleaner" way of doing this in Perl? I am not very strong in all mappings, etc., and I am sure there would be a neater way of writing these nested loops.


Solution

  • for ( values %AR ) {
       for my $to_fix ( values %$_ ) {
    
          %$to_fix = (
             default_profile => [
                map { @$_ }
                   values %$to_fix
             ]
          );
    
       }
    }
    
    1. values %$to_fix returns the array refs.
    2. map { @$_ } … flattens them into access rights.
    3. [ … ] stores them into an array.
    4. %$to_fix = ( default_profile => … ) replaces the existing contents with the new content.

    Your code does not place the access rights in any particular order, and neither does mine. Your key names does suggest an order, however. If you want the access rights ordered according to the original key names, you can use something like the following:

    use Sort::Key::Natural qw( natsort );
    
    %$to_fix = (
       default_profile => [
          map { $to_fix->{ $_ }->@* }
             natsort
                keys %$to_fix
       ]
    );
    

    Alternatively, it might make more sense to remove duplicate access rights and/or sort them by name.

    use List::Util         qw( uniq );
    use Sort::Key::Natural qw( natsort );
    
    %$to_fix = (
       default_profile => [
          natsort              # Sorts the rights. Optional.
             uniq              # Removes duplicates. Optional.
                map { @$_ }
                   values %$to_fix
       ]
    );