For each object in array in perl - perl

I am trying to loop through each object in an array in Perl and I think I am making an obvious error.
my #members_array = [
{
id => 1234,
email => 'first#example.com',
}, {
id => 4321,
email => 'second#example.com',
}
];
use Data::Dumper;
for my $member ( #members_array ) {
print Dumper( $member );
}
Expected output for first iteration
{
id => 1234,
email => 'first#example.com',
}
Actual output for first iteration
[{
'email' => 'first#example.com',
'id' => 1234
}, {
'email' => 'second#example.com',
'id' => 4321
}];
How do I loop through these elements in the array? Thanks!

[ ... ] is used to create an array reference; you need to use ( ... ) to create an array :
my #members_array = (
{
id => 1234,
email => 'first#example.com',
}, {
id => 4321,
email => 'second#example.com',
}
);
And then the rest of your code will work just fine.

Related

How can I redact some values in the dump of a Perl hash?

supposed I have these hashes:
my $hash1 = {
firstname => 'john',
lastname => 'doe',
};
my $hash2_nested = {
name => {
firstname => 'jean',
lastname => 'doe',
}
};
Note: hashes can be nested x times deeply.
I want to use Data::Dumper where I can print the copy of those hashes, but with hidden lastname.
means, it should print out:
$VAR1 = {
'firstname' => 'john'
'lastname' => '***',
};
and this:
$VAR1 = {
'name' => {
'firstname' => 'john'
'lastname' => '***',
}
};
is there any Perl library where it search for a hash key recursively and replace its value dynamically? something like:
replace_hash_value($hash1, 'lastname', '***');
There are several things to consider here. Mostly, you don't want to reinvent what is already out there. Also remember that any Personal Identifying Information (PII) in your program has a way to leak out despite your best efforts, but that's not the programming question at hand.
First, you don't want to operate on the original data, and since you have nested structures, you can't simply make a copy because that only copies the top level and still shares references at the lower level:
my %copy = %original; # shallow copy!
But, the core module Storable can make a deep copy that is completely disconnected, new copy that shares no references:
use Storable qw(dclone);
my $deep_copy = dclone $hash1;
Now you can play with $deep_copy without changing $hash1. You want to find all the last_name keys and remove their value. Grinnz suggested the Data::Walk module (an example of the Visitor design pattern). It's like File::Find for data structures. It's going to handle all the business of finding the hashes for you. In your wanted subroutine, skip everything that's not interesting, then change the nodes that are interesting. You don't worry about how you find or are given the nodes:
use Data::Walk;
walk \&wanted, $deep_copy;
sub wanted {
return unless ref $_ eq ref {};
return unless exists $_->{last_name};
$_->{last_name} = '****';
}
Now, put that all together. Here's a mix of nested things, with some odd cases thrown in, including an object that uses a hash:
use v5.10;
use Hash::AsObject;
my $data = {
first_name => 'Amelia',
last_name => 'Camel',
friends => [
q(last_name => 'REDACTED BY POLICY'),
{
first_name => 'Camelia',
last_name => 'Butterfly',
},
{
first_name => 'Larry',
last_name => 'Llama',
associate => {
first_name => 'Vicky',
last_name => 'Vicuna',
}
},
],
name => {
first_name => 'Andy',
last_name => 'Alpaca',
},
object => bless {
first_name => 'Peter',
last_name => 'Python',
}, 'FooBar',
};
use Storable qw(dclone);
my $deep_copy = dclone( $data );
use Data::Walk;
walk \&wanted, $deep_copy;
use Data::Dumper;
say Dumper( $deep_copy );
sub wanted {
return unless ref $_ eq ref {};
return unless exists $_->{last_name};
$_->{last_name} = '****';
}
And, here's the output from Data::Dumper (which you can prettify with some of its settings):
$VAR1 = {
'object' => bless( {
'first_name' => 'Peter',
'last_name' => 'Python'
}, 'Hash::AsObject' ),
'first_name' => 'Amelia',
'last_name' => '****',
'friends' => [
'last_name => \'REDACTED BY POLICY\'',
{
'last_name' => '****',
'first_name' => 'Camelia'
},
{
'last_name' => '****',
'first_name' => 'Larry',
'associate' => {
'first_name' => 'Vicky',
'last_name' => '****'
}
}
],
'name' => {
'first_name' => 'Andy',
'last_name' => '****'
}
};
Notice that it finds the hashes in the array reference, it doesn't touch the object, and it doesn't touch the literal data that has last_name => in it.
If you don't like those behaviors, then you can modify what you do in wanted to account for what you'd like to happen. Suppose you want to look at certain objects too, like that Hash::AsObject object. One (polymorphic) way to do that is look for objects that let you call a last_name method (although this assumes you can give it an argument to change the last name):
sub wanted {
if( ref $_ eq ref {} and exists $_->{last_name} ) {
$_->{last_name} = '****';
}
# merely one way to do this
elsif( eval { $_->can('last_name') } ) {
$_->last_name( '****' );
}
}
Now the last_name member in the object is also redacted:
$VAR1 = {
'first_name' => 'Amelia',
'friends' => [
'last_name => \'REDACTED BY POLICY\'',
{
'last_name' => '****',
'first_name' => 'Camelia'
},
{
'first_name' => 'Larry',
'associate' => {
'first_name' => 'Vicky',
'last_name' => '****'
},
'last_name' => '****'
}
],
'last_name' => '****',
'name' => {
'first_name' => 'Andy',
'last_name' => '****'
},
'object' => bless( {
'first_name' => 'Peter',
'last_name' => '****'
}, 'Hash::AsObject' )
};
That wanted is as flexible as you'd like it to be, and it's pretty simple.
Why not to code such subroutine yourself?
use strict;
use warnings;
use feature 'say';
my $hash1 = {
firstname => 'john',
lastname => 'doe'
};
my $hash2_nested = {
name => {
firstname => 'jean',
lastname => 'doe'
}
};
my $mask = 'lastname';
hash_mask($hash1,$mask);
hash_mask($hash2_nested,$mask);
sub hash_mask {
say "\$VAR = {";
hash_mask_x(shift, shift, 1);
say "};";
}
sub hash_mask_x {
my $hash = shift;
my $mask_k = shift;
my $depth = shift;
my $indent = ' ' x 8;
my $space = $indent x $depth;
while( my($k,$v) = each %{$hash} ) {
if (ref $v eq 'HASH') {
say $space . "$k => {";
hash_mask_x($v,$mask_k,$depth+1);
say $space . "}";
} elsif( $k eq $mask_k ) {
say $space . "'$k' => '*****'";
} else {
say $space . "'$k' => '$v'";
}
}
}
Output
$VAR = {
'lastname' => '*****'
'firstname' => 'john'
};
$VAR = {
name => {
'lastname' => '*****'
'firstname' => 'jean'
}
};

Iterate an array reference and convert to hash in perl

I have an hash (Printed by Dumper) which is described below
$VAR1 = {
'items' => [
{
'name' => 'test1',
'id' => '1',
'desc' => 'desc1',
},
{
'name' => 'test2',
'id' => '2',
'desc' => 'desc2',
}
],
};
I need to convert "items" which is array reference to a hash like below. ('items' will be a hash of hash with the value of 'id' being the key)
$VAR1 = {
'items' => {
'1' =>{
'name' => 'test1',
'id' => '1',
'desc' => 'desc1',
},
'2' => {
'name' => 'test2',
'id' => '2',
'desc' => 'desc2',
}
}
};
Lets start with the below code. (Assume $data represents the original data and $newitems represents the modified items)
my $data;
my $items = $data->{items};
my %newitems;
foreach my $element (#$items) {
......
}
This looks like an XY problem to me - I'm guessing you're trying to transform some XML, so I'd suggest you want to look upstream to solve this problem.
But on the offchance you're not, then:
$data -> {items} = { map { $_ -> {id} => $_ } #{$data->{items} } };

Inserting one hash into another using Perl

I've tried many different versions of using push and splice, but can't seem to combine two hashes as needed. Trying to insert the second hash into the first inside the 'Item' array:
(
ItemData => { Item => { ItemNum => 2, PriceList => "25.00", UOM => " " } },
)
(
Alternate => {
Description => "OIL FILTER",
InFile => "Y",
MfgCode => "FRA",
QtyAvailable => 29,
Stocked => "Y",
},
)
And I need to insert the second 'Alternate' hash into the 'Item' array of the first hash for this result:
(
ItemData => {
Item => {
Alternate => {
Description => "OIL FILTER",
InFile => "Y",
MfgCode => "FRA",
QtyAvailable => 29,
Stocked => "Y",
},
ItemNum => 2,
PriceList => "25.00",
UOM => " ",
},
},
)
Can someone suggest how I can accomplish this?
Assuming you have two hash references, this is straight-forward.
my $item = {
'ItemData' => {
'Item' => {
'PriceList' => '25.00',
'UOM' => ' ',
'ItemNum' => '2'
}
}
};
my $alt = {
'Alternate' => {
'MfgCode' => 'FRA',
'Description' => 'OIL FILTER',
'Stocked' => 'Y',
'InFile' => 'Y',
'QtyAvailable' => '29'
}
};
$item->{ItemData}->{Item}->{Alternate} = $alt->{Alternate};
The trick here is not to actually merge $alt into some part of $item, but to only take the specific part you want and put it where you want it. We take the Alternate key from $alt and put it's content into a new Alternate key inside the guts of $item.
Adam Millerchip pointed out in a hence deleted comment that this is not a copy. If you alter any of the keys inside of $alt->{Alternative} after sticking it into $item, the data will be changed inside of $item as well because we are dealing with references.
$item->{ItemData}->{Item}->{Alternate} = $alt->{Alternate};
$alt->{Alternate}->{InFile} = 'foobar';
This will actually also change the value of $item->{ItemData}->{Item}->{Alternate}->{InFile} to foobar as seen below.
$VAR1 = {
'ItemData' => {
'Item' => {
'ItemNum' => '2',
'Alternate' => {
'Stocked' => 'Y',
'MfgCode' => 'FRA',
'InFile' => 'foobar',
'Description' => 'OIL FILTER',
'QtyAvailable' => '29'
},
'UOM' => ' ',
'PriceList' => '25.00'
}
}
};
References are supposed to do that, because they only reference something. That's what's good about them.
To make a real copy, you need to dereference and create a new anonymous hash reference.
# create a new ref
# deref
$item->{ItemData}->{Item}->{Alternate} = { %{ $alt->{Alternate} } };
This will create a shallow copy. The values directly inside of the Alternate key will be copies, but if they contain references, those will not be copied, but referenced.
If you do want to merge larger data structures where more than the content of one key needs to be merged, take a look at Hash::Merge instead.

Accessing data within a Perl variable

I dumped out a Perl variable called $prefs and got this:
$VAR1 = bless( {
'USERID' => 1286,
'PREFS' => {
'1' => {
'VALUE' => 1,
'OTHERS_POST' => 1,
'CLIENTS_POST' => 1,
'ASSIGNED_TASKS' => 1
}
},
'dbh' => bless( {
'_sth' => bless( {}, 'DBI::st' ),
'_dbh' => bless( {}, 'DBI::db' )
}, 'Taskman::DB' )
}, 'USystems::UserPrefs' );
I'm pretty new to Perl, and I was wondering if someone can break down on whether or not it is possible to access specific data within this variable.
Like if I wanted to do an if statement such as
if (OTHERS_POST == 1) {
// code }
How would I get to the actual OTHERS_POST inside the $prefs variable
$prefs->{PREFS}->{1}->{OTHERS_POST};

How can I store a list of references in Perl?

I would like to create a list of records. I can add one record just fine:
my $records = [
{ ID => 5342755,
NAME => 'Me',
} ,
];
When I try to add another record, the other records disappear:
$records = [ {
ID => 1212121,
} ];
What is the problem and how can I resolve this?
The problem is you are overwritting the value of $record so that there is only ever one value in the array. Perhaps try the following instead:
my $records = [
{ ID => 5342755,
NAME => 'Me',
} ,
];
push #$records, {
ID => 1212121,
};
You override your variable...
When you declare:
my $records = [date structure here];
you really declare an array ref, if you are a newbie then try (more intuitive)
my #records = (
{
ID => 54321,
NAME => 'bar',
} ,
);
push #records, {ID => 12345, NAME => 'foo'};
print $records[1]->{NAME};
That would print 'foo'