Parse text file into nested data structure - perl

This is my text file:
animal, cola, husband, 36
animal, wilma, wife, 31
animal, pebbles, kid, 4
brutal, george, husband, 41
brutal, jane, wife, 39
brutal, elroy, kid, 9
cosa, homer, husband, 34
cosa, marge, wife, 37
cosa, bart, kid, 11
And this is the data structure I want:
%HASH = (
animal => [
{ name => "cola", role => "husband", age => 36, },
{ name => "wilma", role => "wife", age => 31, },
{ name => "pebbles", role => "kid", age => 4, },
],
brutal => [
{ name => "george", role => "husband", age => 41, },
{ name => "jane", role => "wife", age => 39, },
{ name => "elroy", role => "kid", age => 9, },
],
cosa => [
{ name => "homer", role => "husband", age => 34, },
{ name => "marge", role => "wife", age => 37, },
{ name => "bart", role => "kid", age => 11, },
],
);
I have some pieces of code, but I can't assemble them into a coherent script. I want only for someone to help me to define this structure and to understand it.

Parse each line into a hash.
Remove the key column from the hash.
Push the hash onto an array based on the key column.
Code:
use strict;
use warnings;
use Data::Dumper;
my %hash;
my #columns = qw(category name role age);
while (<DATA>) {
chomp;
my %temp;
#temp{#columns} = split(/\s*,\s*/);
my $key = delete($temp{category});
push(#{$hash{$key}}, \%temp);
}
print Dumper(\%hash);
__DATA__
animal, cola, husband, 36
animal, wilma, wife, 31
animal, pebbles, kid, 4
brutal, george, husband, 41
brutal, jane, wife, 39
brutal, elroy, kid, 9
cosa, homer, husband, 34
cosa, marge, wife, 37
cosa, bart, kid, 11
Output:
$VAR1 = {
'cosa' => [
{
'name' => 'homer',
'age' => '34',
'role' => 'husband'
},
{
'name' => 'marge',
'age' => '37',
'role' => 'wife'
},
{
'name' => 'bart',
'age' => '11',
'role' => 'kid'
}
],
'brutal' => [
{
'name' => 'george',
'age' => '41',
'role' => 'husband'
},
{
'name' => 'jane',
'age' => '39',
'role' => 'wife'
},
{
'name' => 'elroy',
'age' => '9',
'role' => 'kid'
}
],
'animal' => [
{
'name' => 'cola',
'age' => '36',
'role' => 'husband'
},
{
'name' => 'wilma',
'age' => '31',
'role' => 'wife'
},
{
'name' => 'pebbles',
'age' => '4',
'role' => 'kid'
}
]
};

The data structure you show has one critical rule: A value in a hash can only be a scalar.
So to associate a multi-valued variable with a key use the reference to that variable, here arrayref. And if values in that array need be more complex than scalars you again use a reference, here a hashref.† So the value for each key is an arrayref whose elements are hashrefs.
Then you need to learn how to access elements deeper in the structure. That isn't very complex either: dereference them at each level and you can then work with them like you would with an array or hash.
All this is in perldsc, for which one need be clear with perlreftut. A reference is perlref.
When this is put to use in your problem
use warnings;
use strict;
use Data::Dump qw(dd);
my $file = 'data.txt';
open my $fh, '<', $file or die "Can't open $file: $!";
my %result;
while (<$fh>) {
chomp;
my ($key, $name, $role, $age) = split /\s*,\s*/;
push #{$result{$key}},
{ name => $name, role => $role, age => $age };
}
dd \%result;
This prints the correct data structure. I use Data::Dump to see a complex data structure, which need be installed; there is Data::Dumper in the core. There are yet others.
The split above uses a regex /\s*,\s*/ for the delimiter, so to split the line by comma optionally surrounded by spaces. The default for the string to split is $_.
Note that we don't have to "add a key" or make its arrayref-value ahead of using them, as that's done via autovivification. See for example this page and this page and this page.
It is a complex feature that can bite if misused so please read up on it.
† If we attempt to use array or hash variables for array elements we are really trying to fit a list of values into a single "slot" of an array. That can't be done of course, and what happens is that they'll get "flattened" – their elements are merged with all other given scalar elements, and that whole aggregate list populates the array
my #ary = 1..3;
my %hash = (a => 10, b => 20);
# Most likely an error:
my #all = (5, #ary, %hash, 100); #--> (5, 1, 2, 3, a, 10, b, 20, 100)
where key-value pairs may come in any order since hashes are inherently unordered.
Instead, we take references to arrays and hashes and write
my #all = (5, \#ary, \%hash, 100);
Since references are scalars they are legit elements of the array and no flattening happens. So now contents of #ary and %hash keep their individuality and can be recovered as needed.

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'
}
};

Perl Hash of Hashes and Counting

I am trying to process a perl data structure that I have outputted using Data::Dumper
$VAR1 = 'GAHD';
$VAR2 = [
{ 'COUNTRY' => 'US',
'NAME' => 'K. Long',
'DATE_OF_BIRTH' => '7/27/1957',
'POSITION' => 'SENIOR OFFICER',
'AGE' => 57,
'GRADE' => 'P5'
},
{ 'COUNTRY' => 'US',
'NAME' => 'J. Buber',
'DATE_OF_BIRTH' => '12/11/1957',
'POSITION' => 'CHIEF',
'GRADE' => 'D1'
},
{ 'COUNTRY' => 'US',
'NAME' => 'M. Amsi',
'DATE_OF_BIRTH' => '1/1/1957',
'POSITION' => 'SENIOR ANIMAL HEALTH OFFICER',
'AGE' => 57,
'GRADE' => 'P5'
},
{ 'COUNTRY' => 'US',
'NAME' => 'E. Xenu',
'DATE_OF_BIRTH' => '8/31/1964',
'POSITION' => 'SENIOR OFFICER',
'AGE' => 50,
'GRADE' => 'P5'
},
];
$VAR3 = 'GAGD';
$VAR4 = [
{ 'COUNTRY' => 'US',
'NAME' => 'P. Cheru',
'DATE_OF_BIRTH' => '6/18/1966',
'POSITION' => 'ANIMAL PRODUCTION OFFICER',
'AGE' => 48,
'GRADE' => 'P4'
},
{ 'COUNTRY' => 'US',
'NAME' => 'B. Burns',
'DATE_OF_BIRTH' => '2/4/1962',
'POSITION' => 'ANIMAL PRODUCTION OFFICER',
'AGE' => 52,
'GRADE' => 'P4'
},
{ 'COUNTRY' => 'US',
'NAME' => 'R. Mung',
'DATE_OF_BIRTH' => '12/13/1968',
'POSITION' => 'ANIMAL PRODUCTION OFFICER',
'AGE' => 45,
'GRADE' => 'P4'
},
{ 'COUNTRY' => 'GERMANY',
'NAME' => 'B. Scherf',
'DATE_OF_BIRTH' => '8/31/1964',
'POSITION' => 'ANIMAL PRODUCTION OFFICER',
'AGE' => 50,
'GRADE' => 'P4'
},
{ 'COUNTRY' => 'GERMANY',
'NAME' => 'I. Hoffmann',
'DATE_OF_BIRTH' => '2/21/1960',
'POSITION' => 'CHIEF',
'AGE' => 54,
'GRADE' => 'P5'
},
];
The following is outputted:
1 ADG JUNIOR OFFICER K. King
1 DG SENIOR DIRECTOR K. King
3 P5 SENIOR OFFICER R. Forest
R.Forest
K. King
1 P3 JUNIOR OFFICER K. King
3 P1 FORESTRY OFFICER P. Smith
T. Turner
K. Turner
1 P1 GENERAL OFFICER K. King
I would like to count the number of GRADES and POSITIONS by Division. Here is the code that I have put together thus far:
#Push data read from a flat file and while loop
push #{ $grades{ $_->{GRADE} }{ $_->{POSITION} } }, $_->{NAME} for #$AG;
for my $key (
sort { substr( $a, 0, 1 ) cmp substr( $b, 0, 1 ) || substr( $b, 0, 2 ) cmp substr( $a, 0, 2 ) }
keys %grades
)
{
for my $pos ( sort { $a cmp $b } keys %{ $grades{$key} } ) {
my $names = $grades{$key}->{$pos};
my $count = scalar #$names;
print $count, ' ', $key, ' ', $pos, ' ', $names->[0], "\n";
print ' ', $names->[$_], "\n" for 1 .. $#$names;
}
}
The code will stop outputting results if duplicate POSITIONS and GRADES data (i.e. P1, Senior Officer) appear in another Division.
I do not know how to access the Hash of Hash by Division (i.e. GAGD, GAGHD,etc.) so that the same GRADEs and POSITIONs will be outputted per division.
Here is what I really need:
**GAGD**
1 ADG JUNIOR OFFICER K. King
1 DG SENIOR DIRECTOR K. King
3 P5 SENIOR OFFICER R. Forest
R.Forest
K. King
1 P3 JUNIOR OFFICER K. King
3 P1 FORESTRY OFFICER P. Smith
T. Turner
K. Turner
1 P1 GENERAL OFFICER K. King
**GAGHD**
1 P3 JUNIOR OFFICER P. Green
3 P1 FORESTRY OFFICER R. Brown
F. Boo
K. Church
1 P1 GENERAL OFFICER D. Peefer
etc.
etc.
It seems you want to hash the information by Division, then count and store names by grade + position. The following seems to work for me:
#!/usr/bin/perl
use warnings;
use strict;
use feature qw{ say };
my %grades = (
GAHD => [ {
NAME => 'K. Long',
POSITION => 'SENIOR OFFICER',
GRADE => 'P5'
},
{
NAME => 'J. Buber',
POSITION => 'CHIEF',
GRADE => 'D1'
},
{
NAME => 'M. Amsi',
POSITION => 'SENIOR ANIMAL HEALTH OFFICER',
GRADE => 'P5'
},
{
NAME => 'E. Xenu',
POSITION => 'SENIOR OFFICER',
GRADE => 'P5'
},
],
GAGD => [
{
NAME => 'P. Cheru',
POSITION => 'ANIMAL PRODUCTION OFFICER',
GRADE => 'P4'
},
{
NAME => 'B. Burns',
POSITION => 'ANIMAL PRODUCTION OFFICER',
GRADE => 'P4'
},
{
NAME => 'R. Mung',
POSITION => 'ANIMAL PRODUCTION OFFICER',
GRADE => 'P4'
},
{
NAME => 'B. Scherf',
POSITION => 'ANIMAL PRODUCTION OFFICER',
GRADE => 'P4'
},
{
NAME => 'I. Hoffmann',
POSITION => 'CHIEF',
GRADE => 'P5'
},
]);
for my $division (keys %grades) {
say "**$division**";
my %group;
for my $person (#{ $grades{$division} }) {
my $position = join ' ', #{ $person }{qw{GRADE POSITION}};
push #{ $group{$position} }, $person->{NAME};
}
for my $position (keys %group) {
say join ' ', scalar #{ $group{$position} },
$position,
$group{$position}[0];
my #remaining_names = #{ $group{$position} };
shift #remaining_names;
say "\t$_" for #remaining_names;
}
say q();
}
Update
If you store more information than a name for a person in an array ref (push push #{ $group{$position} }, [ ... ];), you can then retrieve it by dereferencing each reference, for example in map:
say join ' ', scalar #{ $group{$position} },
$position,
join "\n\t", map "#$_", #{ $group{$position} };
You're almost there with the code that you've got. Assuming that the hash you've printed out is called %grades, I would do the following:
foreach my $g (sort keys %$grades) {
print "**$g**\n";
# put the info to be printed in a temporary hash
my %temp;
foreach (#{$grades->{$g}}) {
push #{$temp{ $_->{GRADE}." ".$_->{POSITION} }}, $_->{NAME};
}
foreach (sort keys %temp) {
# print a count of the number of names, then the grade/position info
print scalar #{$temp{$_}} . " $_ "
# #{$temp{$_}} holds the names, so just sort them and print them out.
. join("\n\t\t\t", sort #{$temp{$_}}) . "\n";
}
}

perl XML::Simple for repeated elements

I have the following xml code
<?xml version="1.0"?>
<!DOCTYPE pathway SYSTEM "http://www.kegg.jp/kegg/xml/KGML_v0.7.1_.dtd">
<!-- Creation date: Aug 26, 2013 10:02:03 +0900 (GMT+09:00) -->
<pathway name="path:ko01200" >
<reaction id="14" name="rn:R01845" type="irreversible">
<substrate id="108" name="cpd:C00447"/>
<product id="109" name="cpd:C05382"/>
</reaction>
<reaction id="15" name="rn:R01641" type="reversible">
<substrate id="109" name="cpd:C05382"/>
<substrate id="104" name="cpd:C00118"/>
<product id="110" name="cpd:C00117"/>
<product id="112" name="cpd:C00231"/>
</reaction>
</pathway>
I am trying to print the substrate id and product id with following code which I am stuck for the one that have more than one ID. Tried to use dumper to see the data structure but I don't know how to proceed. I have already used XML simple for the rest of my parsing script (this part is a small part of my whole script ) and I can not change that now
use strict;
use warnings;
use XML::Simple;
use Data::Dumper;
my $xml=new XML::Simple;
my $data=$xml->XMLin("test.xml",KeyAttr => ['id']);
print Dumper($data);
foreach my $reaction ( sort keys %{$data->{reaction}} ) {
print $data->{reaction}->{$reaction}->{substrate}->{id}."\n";
print $data->{reaction}->{$reaction}->{product}->{id}."\n";
}
Here is the output
$VAR1 = {
'name' => 'path:ko01200',
'reaction' => {
'15' => {
'substrate' => {
'104' => {
'name' => 'cpd:C00118'
},
'109' => {
'name' => 'cpd:C05382'
}
},
'name' => 'rn:R01641',
'type' => 'reversible',
'product' => {
'112' => {
'name' => 'cpd:C00231'
},
'110' => {
'name' => 'cpd:C00117'
}
}
},
'14' => {
'substrate' => {
'name' => 'cpd:C00447',
'id' => '108'
},
'name' => 'rn:R01845',
'type' => 'irreversible',
'product' => {
'name' => 'cpd:C05382',
'id' => '109'
}
}
}
};
108
109
Use of uninitialized value in concatenation (.) or string at line 12.
Use of uninitialized value in concatenation (.) or string at line 13.
First of all, don't use XML::Simple. it is hard to predict what exact data structure it will produce from a bit of XML, and it's own documentation mentions it is deprecated.
Anyway, your problem is that you want to access an id field in the product and substrate subhashes – but they don't exist in one of the reaction subhashes
'15' => {
'substrate' => {
'104' => {
'name' => 'cpd:C00118'
},
'109' => {
'name' => 'cpd:C05382'
}
},
'name' => 'rn:R01641',
'type' => 'reversible',
'product' => {
'112' => {
'name' => 'cpd:C00231'
},
'110' => {
'name' => 'cpd:C00117'
}
}
},
Instead, the keys are numbers, and each value is a hash containing a name. The other reaction has a totally different structure, so special-case code would have been written for both. This is why XML::Simple shouldn't be used – the output is just to unpredictable.
Enter XML::LibXML. It is not extraordinary, but it implememts standard APIs like the DOM and XPath to traverse your XML document.
use XML::LibXML;
use feature 'say'; # assuming perl 5.010
my $doc = XML::LibXML->load_xml(file => "test.xml") or die;
for my $reaction_item ($doc->findnodes('//reaction/product | //reaction/substrate')) {
say $reaction_item->getAttribute('id');
}
Output:
108
109
109
104
110
112

How do I access certain keys in a Perl nested hash?

I dumped a data structure:
print Dumper($bobo->{'issues'});
and got:
$VAR1 = {
'155' => {
'name' => 'Gender',
'url_name' => 'gender'
}
};
How can I extract 155?
How about if I have:
$VAR1 = {
'155' => {'name' => 'Gender', 'url_name' => 'gender'},
'11' => {'name' => 'Toddler', 'url_name' => 'toddler'},
'30' => {'name' => 'Lolo', 'url_name' => 'lolo'}
};
I want to print one key, i.e. the first or second to see the value of the key?
So, based on the example you posted, the hash looks like this:
$bobo = {
issues => {
155 => {
name => 'Gender',
url_name => 'gender',
},
},
};
'155' is a key in your example code. To extract a key, you would use keys.
my #keys = keys %{$bobo->{issues}};
But to get the value that 155 indexes, you could say:
my $val = $bobo->{issues}{155};
Then $val would contain a hashref that looks like this:
{
name => 'Gender',
url_name => 'gender'
}
Have a look at perldoc perlreftut.
It is a key in the hash referenced by $bobo->{'issues'}. So you would iterate through
keys %{$bobo->{'issues'}}
to find it.

How do I access values in the data structure returned by XML::Simple?

Hi everyone,
This is very simple for perl programmers but not beginners like me,
I have one xml file and I processed using XML::Simple like this
my $file="service.xml";
my $xml = new XML::Simple;
my $data = $xml->XMLin("$file", ForceArray => ['Service','SystemReaction',
'Customers', 'Suppliers','SW','HW'],);
Dumping out $data, it looks like this:
$data = {
'Service' => [{
'Suppliers' => [{
'SW' => [
{'Path' => '/work/service.xml', 'Service' => 'b7a'},
{'Path' => '/work/service1.xml', 'Service' => 'b7b'},
{'Path' => '/work/service2.xml', 'Service' => 'b5'}]}
],
'Id' => 'SKRM',
'Customers' =>
[{'SW' => [{'Path' => '/work/service.xml', 'Service' => 'ASOC'}]}],
'Des' => 'Control the current through the pipe',
'Name' => ' Control unit'
},
{
'Suppliers' => [{
'HW' => [{
'Type' => 'W',
'Path' => '/work/hardware.xml',
'Nr' => '18',
'Service' => '1'
},
{
'Type' => 'B',
'Path' => '/work/hardware.xml',
'Nr' => '7',
'Service' => '1'
},
{
'Type' => 'k',
'Path' => '/work/hardware.xml',
'Nr' => '1',
'Service' => '1'
}]}
],
'Id' => 'ADTM',
'Customers' =>
[{'SW' => [{'Path' => '/work/service.xml', 'Service' => 'SDCR'}]}],
'Des' => 'It delivers actual motor speed',
'Name' => ' Motor Drivers and Diognostics'
},
# etc.
],
'Systemreaction' => [
# etc.
],
};
How to access each elements in the service and systemReaction(not provided). because I am using "$data" in further processing. So I need to access each Id,customers, suppliers values in each service. How to get particular value from service to do some process with that value.for example I need to get all Id values form service and create nodes for each id values.
To get Type and Nr value I tried like this
foreach my $service (#{ $data->{Service}[1]{Suppliers}[0]{HW}[0] }) {
say $service->{Nr};
}
foreach my $service (#{ $data->{Service}[1]{Suppliers}[0]{HW}[0] }) {
say $service->{Type};
}
can you help me how to get all Nr and Type values from Supplier->HW.
I suggest reading perldocs Reference Tutorial and References and Nested Data Structures. They contain an introduction and full explanation of how to access data like that.
But, for example, you can access the service ID by doing:
say $data->{Service}[0]{Id} # prints SKRM
You could go through all the services, printing their ID, with a loop:
foreach my $service (#{ $data->{Service} }) {
say $service->{Id};
}
In response to your edit
$data->{Service}[1]{Suppliers}[0]{HW}[0] is an hash reference (you can check this quickly by either using Data::Dumper or Data::Dump on it, or just the ref function). In particular, it is { Nr => 18, Path => "/work/hardware.xml", Service => 1, Type => "W" }
In other words, you've almost got it—you just went one level too deep. It should be:
foreach my $service (#{ $data->{Service}[1]{Suppliers}[0]{HW} }) {
say $service->{Nr};
}
Note the lack of the final [0] that you had.