Hash value overwrite due to similar keys? - perl

I want to map data from a German data structure to an English one.
For that I use a hash which holds the German words as keys and the English ones as values ($mapping_table).
The data are stored in an array of hashes ($data). The keys are the German words which have to be replaced by English ones. The values are the data which shall stay unchanged.
To do the mapping I have written the following code:
my $mapping_table = {
'Exemplare' => 'copies',
'Seiten' => 'pages',
'Statushinweis' => 'status',
'Serie von' => 'number_of',
'ISBN/Barcode-Nr.' => 'ISBN_barcode',
'Status' => 'status',
};
my $data = [
{
'Exemplare' => '1',
'Seiten' => '0',
'Statushinweis' => 'Statushinweis',
'ISBN/Barcode-Nr.' => '3-551-01561-9',
'Serie von' => '4',
'Status' => 'Gesucht'
},
{
'Exemplare' => '4',
'Seiten' => '111',
'Statushinweis' => '',
'ISBN/Barcode-Nr.' => '3-551-01561-9',
'Serie von' => '4',
'Status' => 'Vorhanden'
}
];
my $mapped_data = [];
foreach my $issue ( #$data ) {
my %tmp_hash;
foreach my $key (sort keys %$mapping_table) {
$tmp_hash{$mapping_table->{$key}} = $issue->{$key};
}
push #$mapped_data, \%tmp_hash;
}
print Dumper $mapped_data;
The result of the dump surprises me a lot.
$VAR1 = [
{
'number_of' => '4',
'copies' => '1',
'status' => 'Statushinweis',
'ISBN_barcode' => '3-551-01561-9',
'pages' => '0'
},
{
'pages' => '111',
'ISBN_barcode' => '3-551-01561-9',
'status' => '',
'copies' => '4',
'number_of' => '4'
}
];
It can be seen that the values of the keys 'status' hold those of the keys 'Statushinweis' whereas these keys got completely lost.
I tried to figure out why this happens with the help of Google and the debugger but failed (maybe to obvious to be seen?).
Maybe I should mention that I work with strawberry perl 5.26.1 on Win7.
Any idea what I did wrong and how to fix it?
Thx in advance.

Your mapping table needs to support bi-directional mapping. Currently, you cannot map from "value" (the, well, value) to "Status" or "Statushinweis", because both keys have the same value. If you can choose your keys freely, rename one of them, for instance:
my $mapping_table = {
'Exemplare' => 'copies',
'Seiten' => 'pages',
'Statushinweis' => 'statushint', // <- change here
'Serie von' => 'number_of',
'ISBN/Barcode-Nr.' => 'ISBN_barcode',
'Status' => 'status',
};
my $data = [
{
'Exemplare' => '1',
'Seiten' => '0',
'Statushinweis' => 'Statushinweis',
'ISBN/Barcode-Nr.' => '3-551-01561-9',
'Serie von' => '4',
'Status' => 'Gesucht'
},
{
'Exemplare' => '4',
'Seiten' => '111',
'Statushinweis' => '',
'ISBN/Barcode-Nr.' => '3-551-01561-9',
'Serie von' => '4',
'Status' => 'Vorhanden'
}
];

Related

How to retrieve hash value from session in Perl

I am storing a hash in session. I am trying to retrieve the values from session, but is not successful. I want to loop through the hash value in session to generate a select box. Below is the session value
$VAR1 = {
'userDetails' => {
'roles' => [
{
'ln' => 'asdf',
'email' => 'test#example.com',
'session_id' => '14',
'is_active' => '0',
'role' => 'ndfbfd',
'facility_name' => 'jjjj',
'fn' => 'yyyyyy'
},
{
'ln' => 'asdf',
'email' => 'test#example.com',
'session_id' => '15',
'is_active' => '1',
'role' => 'ndfbfd',
'facility_name' => 'fbhsdf',
'fn' => 'yyyyyy'
},
{
'ln' => 'asdf',
'email' => 'test#example.com',
'session_id' => '16',
'is_active' => '1',
'role' => 'ndfbfd',
'facility_name' => 'mvsd',
'fn' => 'yyyyyy'
},
{
'ln' => 'asdf',
'email' => 'test#example.com',
'session_id' => '17',
'is_active' => '1',
'role' => 'bdfgre',
'facility_name' => 'jjjj',
'fn' => 'yyyyyy'
},
{
'ln' => 'asdf',
'email' => 'test#example.com',
'session_id' => '18',
'is_active' => '0',
'role' => 'gderere',
'facility_name' => 'jjjj',
'fn' => 'yyyyyy'
}
],
'ln' => 'asdf',
'logged_in' => '1',
'fn' => 'yyyyyy'
},
'logged_in' => '1',
'username' => 'test#example.com'
};
I am trying to retrieve the roles from the session
my %userDetails = $self->session('userDetails');
my %roles = $userDetails{'roles'};
foreach my $family ( keys %roles ) {
print "$family: { ";
for my $role ( keys %{ $HoH{$family} } ) {
print "$role=$HoH{$family}{$role} ";
}
print "}\n";
}
It is showing two errors.
Reference found where even-sized list expected.
Odd number of elements in hash assignment.
When I change the code my %userDetails = $self->session('userDetails'); to my %userDetails = \$self->session('userDetails'); I am getting the error
Odd number of elements in hash assignment.
Odd number of elements in hash assignment.
Just guessing blatantly
my %userDetails = %{$self->session('userDetails')};
my #roles = #{$userDetails{'roles'}};
require Data::Dumper;
foreach my $family ( #roles ) {
print Data::Dumper::Dumper($family);
}
In your example
'roles' => [
so this indicates that the value beneath the role attribute is an array reference.
$self->session('userDetails')
might be hash, or more probably a hash-reference. I guessed the later. So if you have a reference to an "element" you derefence it first. See perldoc perlref

Hashes of Arrays of Hashes of Arrays

I have the following output from hash in perl:
$VAR1 = {
'ins_api' => {
'sid' => 'eoc',
'outputs' => {
'output' => [
{
'body' => {
'TABLE_interface' => {
'ROW_interface' => [
{
'vdc_lvl_in_pkts' => 17081772,
'vdc_lvl_in_avg_bits' => 3128,
'eth_autoneg' => 'on',
'eth_speed' => '1000 Mb/s',
'admin_state' => 'up',
'vdc_lvl_out_mcast' => '65247',
'state' => 'up',
'eth_mtu' => '1500',
'eth_hw_addr' => '78ba.f9ad.b248',
'eth_mdix' => 'off',
'interface' => 'mgmt0',
'eth_ip_addr' => '10.56.32.84',
'eth_bw' => 1000000,
'vdc_lvl_in_avg_pkts' => '3',
'vdc_lvl_out_bytes' => '3463952330',
'vdc_lvl_in_ucast' => '7653891',
'eth_ip_prefix' => '10.',
'eth_rxload' => '1',
'eth_txload' => '1',
'eth_reliability' => '255',
'eth_dly' => 10,
'vdc_lvl_in_mcast' => '8742911',
'eth_ip_mask' => 24,
'eth_bia_addr' => '78ba.f9ad.b248',
'eth_duplex' => 'full',
'vdc_lvl_out_pkts' => '8668507',
'vdc_lvl_out_avg_pkts' => '1',
'vdc_lvl_in_bcast' => '684970',
'vdc_lvl_out_avg_bits' => '1840',
'medium' => 'broadcast',
'vdc_lvl_out_bcast' => '5',
'vdc_lvl_out_ucast' => '8603255',
'eth_ethertype' => '0x0000',
'vdc_lvl_in_bytes' => '1985125644',
'eth_hw_desc' => 'GigabitEthernet'
},
{
'eth_babbles' => '0',
'eth_outbytes' => '7362149107971',
'eth_outucast' => '16348249961',
'eth_clear_counters' => 'never',
'eth_watchdog' => '0',
'eth_inpkts' => 8644872191,
'eth_inbytes' => '3415386845315',
'eth_out_flowctrl' => 'off',
'eth_bad_proto' => '0',
'eth_frame' => '0',
----- output omitted -------
},
What would be the best way to loop via ROW_interface array and print some of the elements? I am just trying to get the elements in the ROW_interface array.
my $ROW_Interfaces = $output->{body}{TABLE_interface}{ROW_interface};
for my $ROW_Interfaces (#$ROW_Interfaces) {
...
}
It seems there can be more than one output, so you'll have to locate the appropriate one similarly.
Similar to #ikegami's answer, but handles the multiple output entries. The if defined... is there as the structure isn't complete, and I wasn't sure if each entry had the same keys or not.
for my $output (#{ $VAR1->{ins_api}{outputs}{output} }){
for my $row_int (#{ $output->{body}{TABLE_interface}{ROW_interface} }){
print "$row_int->{eth_frame}\n" if exists $row_int->{eth_frame};
}
}

Accessing a nested data structure

I have an array of hashes nested to multiple levels. I need to extract a value from all deeply-nested hashes that have a given value for a different key in the same hash
This is a collection of entities from our database, and the data represents contacts within each entity and all of their contact values.
There is a hash key contact_method_type_id which refers to an integer defining the type of contact method. The contact_method_type_id that I care about is 1, which is email.
The first contact has three different contact_methods. The first is 4 which is an office phone, the second is a 2 which is a home phone, and the third is a 1 which is email.
Within the same hash is there is a 'contact_method_value', which is the string representation of their email address.
I need a way to extract just these values into a new array
Here are the contents of the first element of the array
$VAR1 = [
{ 'total' => '2',
'results' => [
{ 'contact_type_name' => 'Primary Technical Contact',
'street' => undef,
'state_id' => undef,
'state_name' => undef,
'last_name' => 'Barb',
'entities' => [
{ 'entity_name' => 'XXXXX',
'entity_id' => 'XXXXX'
}
],
'state_abbr_name' => undef,
'city' => undef,
'country_id' => undef,
'latitude' => undef,
'contact_id' => 'XXXXXX',
'contact_type_id' => '1',
'roles' => [],
'contact_methods' => [
{ 'entity_name' => undef,
'contact_method_value' => 'XXXXXXX',
'contact_method_type_id' => '4',
'contact_method_id' => '24041',
'entity_id' => undef,
'contact_method_type_name' => 'Cell Phone'
},
{ 'entity_name' => undef,
'contact_method_value' => 'XXXXXX',
'contact_method_type_id' => '2',
'contact_method_id' => '24051',
'entity_id' => undef,
'contact_method_type_name' => 'Office Phone'
},
{ 'entity_name' => undef,
'contact_method_value' => 'example#example.com',
'contact_method_type_id' => '1',
'contact_method_id' => '24061',
'entity_id' => undef,
'contact_method_type_name' => 'Email'
}
],
'country_name' => undef,
'longitude' => undef,
'country_abbr_name' => undef,
'full_name' => 'NAME',
'networks' => [
{ 'network_name' => 'NET',
'network_id' => 'X'
}
],
'timezone_id' => undef,
'zip' => undef,
'timezone_name' => undef,
'title' => 'MAC/Network Specialist',
'first_name' => 'Terri'
},
{ 'contact_type_name' => 'Primary Technical Contact',
'street' => 'STREET',
'state_id' => undef,
'state_name' => undef,
'last_name' => 'NAME',
'entities' => [
{ 'entity_name' => 'NAME',
'entity_id' => '2679'
}
],
'state_abbr_name' => undef,
'city' => 'CITY',
'country_id' => undef,
'latitude' => undef,
'contact_id' => '7896',
'contact_type_id' => '1',
'roles' => [],
'contact_methods' => [
{ 'entity_name' => undef,
'contact_method_value' => 'example#example.com',
'contact_method_type_id' => '1',
'contact_method_id' => '16796',
'entity_id' => undef,
'contact_method_type_name' => 'Email'
},
{ 'entity_name' => undef,
'contact_method_value' => 'number',
'contact_method_type_id' => '2',
'contact_method_id' => '16797',
'entity_id' => undef,
'contact_method_type_name' => 'Office Phone'
}
],
'country_name' => undef,
'longitude' => undef,
'country_abbr_name' => undef,
'full_name' => 'NAME',
'networks' => [
{ 'network_name' => 'net',
'network_id' => '17'
}
],
'timezone_id' => undef,
'zip' => 'zip',
'timezone_name' => undef,
'title' => 'Infrastructure Manager',
'first_name' => 'name'
}
],
'offset' => '0'
},
...
This looks suspiciously like something that XML::Simple would have generated.
Assuming this is the case, then I would suggest that you've fallen for the classic mistake of assuming XML::Simple actually helps.
Under that assumption, if you instead use XML::Twig:
Taking your $VAR1. Although - ideally you'll just parse the original source with parse or parsefile:
use XML::Twig;
use XML::Simple;
my $twig = XML::Twig->parse( XMLout($VAR1) );
print $_->att('contact_method_value'), "\n" for $twig->findnodes('//contact_methods[#contact_method_type_name="Email"]');
Which given your sample (as $VAR1):
example#example.com
example#example.com
Edit: Because you've commented that it's JSON then I wouldn't necessarily do this (Although - it does actually work, despite that).
If the data structures are all of the same kind, this is very trivial. You just need to iterate all the outer hashrefs (I called those resultsets). Inside those, you need to look at all results, and in each result you need to look at all the contact methods. If one of them has a contact_method_type_id of 1, you take the contact_method_value. And that's it.
my #email_addresses;
foreach my $resultset ( #{$data} ) {
foreach my $result ( #{ $resultset->{results} } ) {
foreach my $contact ( #{ $result->{contact_methods} } ) {
push #email_addresses , [ $contact->{contact_method_value} ]
if $contact->{contact_method_type_id} == 1;
}
}
}
This code assumes your structure is called $data. #email_addresses looks like this when output.
[
[ 'EMAIL' ],
[ 'EMAIL' ]
];
If you have this on a database then you should use an SQL query to retrieve it, rather than fetching everything into memory and processing what you have
The output from Data::Dumper shows the contents of your data, but it doesn't explain what you're dealing with in your code. Specifically, you don't have a $VAR1 in your code, but I have no idea what you do have
In the end, I think I wouldn't start from here. But since it's the only starting point I have to work with, it's a simple matter of recursing through the data structure
I've assumed that you want
$VAR1->[*]{results}[*]{contact_methods}[*]{contact_method_value}
where
$VAR1->[*]{results}[*]{contact_methods}[*]{contact_method_type_name} eq 'Email'
Update
Since your comments I've altered my code to select the same values where
$VAR1->[*]{results}[*]{contact_methods}[*]{contact_method_type_id} == 1
Since you said nothing about your code at all, I've had to assume a variable $data which contains a reference to the array that you show in your question
for my $item ( #$data ) {
my $results = $item->{results};
for my $result ( #$results ) {
my $methods = $result->{contact_methods} or die;
for my $method ( #$methods ) {
#my $type_name = $method->{contact_method_type_name};
#next unless $type_name eq 'Email';
my $type_id = $method->{contact_method_type_id};
next unless $type_id == 1; ## Email
my $value = $method->{contact_method_value};
print "$value\n";
}
}
}
output
example#example.com
example#example.com

Not an array reference message is thrown when using XML:Simple

I am new to perl and trying to parse XML using XML:Simple
My xml is
<suite suiteId="45" instanceId="3485">
<project>Test project</project>
<testcase id="2346" name="abc" suite="TEst1" priority="1" severity="1" owner="domain" category="BAT" timeout="10">
<description>Checking Test1</description>
<testExecTimeInMins>2</testExecTimeInMins>
<status>Failed</status>
<testServer id="86" name="host1" ip="1.2.3.4" platform="Linux" database="MySQL" buildNo="" />
<error></error>
</testcase>
<testcase id="2346456" name="abc123" suite="TEst2" priority="1" severity="1" owner="domain" category="BAT" timeout="10">
<description>Checking Test2</description>
<testExecTimeInMins>6</testExecTimeInMins>
<status>Passed</status>
<testServer id="86" name="host1" ip="1.2.3.4" platform="Linux" database="MySQL" buildNo="" />
<error />
</testcase>
</suite>
How do I get the values of testcase id, name, suite?
How to get the values for testServer, id, name?
I tried to access it as shown below but it throws "Not an Array reference at"
$XMLData = XMLin($targetFile);
foreach my $testcases (#{$XMLData->{testcase}}){
$logger->info("$testcases->{id}");
}
If you examine the actual data that is coming out of XMLin, you would see that you don't have an array reference (just like the error message states), but instead have a hashref that is keyed on the name of the testcase:
use strict;
use warnings;
use Data::Dumper;
use XML::Simple;
my $xml = XMLin("/Users/mcmillhj/temp.xml");
print Dumper $xml;
__DATA__
{
'instanceId' => '3485',
'project' => 'Test project',
'testcase' => {
'abc123' => {
'owner' => 'domain',
'priority' => '1',
'status' => 'Passed',
'suite' => 'TEst2',
'testExecTimeInMins' => '6',
'description' => 'Checking Test2',
'timeout' => '10',
'error' => {},
'category' => 'BAT',
'id' => '2346456',
'severity' => '1',
'testServer' => {
'database' => 'MySQL',
'buildNo' => '',
'ip' => '1.2.3.4',
'name' => 'host1',
'platform' => 'Linux',
'id' => '86'
}
},
'abc' => {
'owner' => 'domain',
'priority' => '1',
'status' => 'Failed',
'suite' => 'TEst1',
'testExecTimeInMins' => '2',
'description' => 'Checking Test1',
'timeout' => '10',
'error' => {},
'category' => 'BAT',
'id' => '2346',
'severity' => '1',
'testServer' => {
'database' => 'MySQL',
'buildNo' => '',
'ip' => '1.2.3.4',
'name' => 'host1',
'platform' => 'Linux',
'id' => '86'
}
}
},
'suiteId' => '45'
};
To get all testcases, you just need to iterate over the keys:
foreach my $testcasename ( keys %{ $XMLData->{testcase} } ){
$logger->info($XMLdata->{testcase}->{$testcasename}->{id});
}

Combining 2+ 'deep' (multi-dimensional) hashes in perl

There is a question that explains exactly what I want here: how to merge 2 deep hashes in perl
However, the answer there does not seem to work for me (suggestions of using the Merge module).
I have two hashes like so:
$VAR1 = {
'57494' => {
'name' => 'John Smith',
'age' => '9',
'height' => '120'
},
'57495' => {
'name' => 'Amy Pond',
'age' => '17',
'height' => '168'
}
}
};
$VAR1 = {
'57494' => {
'name_address' => 'Peter Smith',
'address' => '5 Cambridge Road',
'post_code' => 'CR5 0FS'
}
}
};
If I use Hash::Merge or the %c = {%a,%b} format I get this every time:
$VAR1 = '57494';
$VAR2 = {
'name_address' => 'Peter Smith',
'address' => '5 Cambridge Road',
'post_code' => 'CR5 0FS'
};
(so it basically overwrote the first data with the second and messed up the keys) when I want:
$VAR1 = {
'57494' => {
'name' => 'John Smith',
'age' => '9',
'height' => '120'
'name_address' => 'Peter Smith',
'address' => '5 Cambridge Road',
'post_code' => 'CR5 0FS'
},
'57495' => {
'name' => 'Amy Pond',
'age' => '17',
'height' => '168'
}
}
};
So when the keys are the same, the data merges together, otherwise the new keys are just appended onto the end. I hope this make sense. Maybe I've done something incorrectly using Merge or need to 'manually' add them in loops, but I'm spending too much time thinking about it, regardless!
Edit: how I use Merge to see if I'm doing something silly:
I have:
use Hash::Merge qw( merge );
...hash data above as %hash1 and %hash2...
my %combined_hash = %{ merge( %hash1,%hash2 ) };
print Dumper(%combined_hash);
If I do it with references, it works like a charm.
use strict; use warnings;
use Data::Dumper;
use Hash::Merge qw(merge);
my $h1 = {
'57494' => {
'name' => 'John Smith',
'age' => '9',
'height' => '120'
},
'57495' => {
'name' => 'Amy Pond',
'age' => '17',
'height' => '168'
}
};
my $h2 = {
'57494' => {
'name_address' => 'Peter Smith',
'address' => '5 Cambridge Road',
'post_code' => 'CR5 0FS'
}
};
my $h3 = merge( $h1, $h2 );
print Dumper $h3;
Output:
$VAR1 = {
'57495' => {
'name' => 'Amy Pond',
'age' => '17',
'height' => '168'
},
'57494' => {
'name_address' => 'Peter Smith',
'name' => 'John Smith',
'post_code' => 'CR5 0FS',
'address' => '5 Cambridge Road',
'height' => '120',
'age' => '9'
}
};
If, however, I do it with hashes instead of hash refs, it doesn't:
my %hash1 = (
'57494' => {
'name' => 'John Smith',
'age' => '9',
'height' => '120'
},
'57495' => {
'name' => 'Amy Pond',
'age' => '17',
'height' => '168'
}
);
my %hash2 = (
'57494' => {
'name_address' => 'Peter Smith',
'address' => '5 Cambridge Road',
'post_code' => 'CR5 0FS'
}
);
my %h3 = merge( %hash1, %hash2 );
print Dumper \%h3;
__END__
$VAR1 = {
'57495' => undef
};
That is because the merge from Hash::Merge can only take references, but you are passing it hashes. In addition, you need to call it in scalar context.
Try it like so:
# +--------+--- references
# ,-- SCALAR context | |
my $combined_hash = %{ merge( \%hash1, \%hash2 ) };
print Dumper($combined_hash);
for my $key (keys %fromhash) {
if(not exists $tohash{$key}) {
$tohash{$key} = {};
}
for my $subkey (keys %{$fromhash{$key}}) {
${$tohash{$key}}{$subkey} = ${$fromhash{$key}}{$subkey};
}
}
With more or less braces depending on whether my last coffee was any good.
Python is definitely more comfortable for this kind of thing, because it doesn't make you think about references:
for key in fromdict:
if key not in todict:
todict[key] = {}
todict[key] = dict(fromdict[key].items() + todict[key].items())
Or if todict is a defaultdict (creating keys on read as well as assignment):
for key in fromdict:
todict[key] = dict(dict(fromdict[key]).items() + dict(todict[key]).items())