bfdi533 has asked for the wisdom of the Perl Monks concerning the following question:
I have some XML that is being returned from a REST API and sometimes there is a single result and sometime there are multiple results.
I am having a hard time determining how to get the data that I need and know if there is an array or not.
Example:
$item->{result}->[0]->{value}
$item->{result}->[1]->{value}
versus
$item->{result}->{value}
I really need to get the value if there is one or know there are more than one value so I can collect them into an array.
What is the best practice for this sort of thing?
Update:
I have been using XML::Simple so far with my code and I do find that it is inconsistent and hard to work with. I will be trying out XML::Rules here shortly per your generous recommendations.
Re: hashref with(out) arrays
by hippo (Bishop) on Nov 20, 2017 at 22:50 UTC
|
"Best practice" is a matter of opinion (see various threads here regarding PBP). That said, I suppose Ref::Util is the best bet.
#!/usr/bin/env perl
use strict;
use warnings;
use Ref::Util 'is_arrayref';
my $item = {
x => 'a',
y => ['b', 'c']
};
for my $key (keys %$item) {
if (is_arrayref $item->{$key}) {
print "Value for $key is an arrayref\n";
} else {
print "Value for $key is not an arrayref\n";
}
}
| [reply] [d/l] |
Re: hashref with(out) arrays
by tangent (Parson) on Nov 21, 2017 at 00:09 UTC
|
my $item1 = {
result => { value => 'a' },
};
my $item2 = {
result => [
{ value => 'b' },
{ value => 'c' },
],
};
parse_result( $item1->{'result'} );
parse_result( $item2->{'result'} );
sub parse_result {
my $result = shift;
if ( ref $result eq 'HASH' ) {
print "Parsing hashref\n";
print "Value: $result->{'value'}\n";
}
elsif ( ref $result eq 'ARRAY' ) {
print "Parsing arrayref\n";
my @values;
for my $item ( @$result ) {
push( @values, $item->{'value'} );
}
print "Values: @values\n";
}
}
OUTPUT
Parsing hashref
Value: a
Parsing arrayref
Values: b c
| [reply] [d/l] [select] |
Re: hashref with(out) arrays
by virtualsue (Vicar) on Nov 21, 2017 at 10:49 UTC
|
| [reply] |
Re: hashref with(out) arrays
by ikegami (Patriarch) on Nov 21, 2017 at 18:10 UTC
|
You appear to be using XML::Simple (or something with the same interface). You are discouraged from using XML::Simple (by its own documentation) because of its awful interface. See Why is XML::Simple “Discouraged”? for more information.
By using a better parser (such as XML::LibXML), the problem goes away:
my @results = $xpc->findnodes('result/value', $item_node);
| [reply] [d/l] |
Re: hashref with(out) arrays
by thanos1983 (Parson) on Nov 20, 2017 at 22:43 UTC
|
Hello bfdi533,
You can choose one of the proposed solutions on a relevant question Is there an isArray() in Perl?.
Hope this helps, BR.
Seeking for Perl wisdom...on the process of learning...not there...yet!
| [reply] [d/l] [select] |
Re: hashref with(out) arrays
by Anonymous Monk on Nov 20, 2017 at 23:00 UTC
|
| [reply] |
Re: hashref with(out) arrays
by glasswalk3r (Friar) on Nov 21, 2017 at 16:01 UTC
|
Without a sample of your code or at least the mention of any modules you're using to consume the REST API, we can only guess what you're using.
Anyway, you can always use the debugger with the "x" command to see the object structure, but you will be better served if you a have an API you can rely on.
Alceu Rodrigues de Freitas Junior
---------------------------------
"You have enemies? Good. That means you've stood up for something, sometime in your life." - Sir Winston Churchill
| [reply] |
Re: hashref with(out) arrays
by bfdi533 (Friar) on Nov 21, 2017 at 15:05 UTC
|
Thank you all for the replies! The use of ref is something I have not ever used before but it is something I will be using more going forward.
I have been using XML::Simple but did not know about the ForceArray which would definitely have made past projects much easier and will also use it in combination with ref.
I have not looked at XML::Rules before but I will give it a glace as well.
| [reply] |
|
I have to caution against XML::Simple, it is really only still useful for reading very simple XML files, and its output is often unreliable - exactly the issue you were asking about in the root node may pop up again later, and often in a way that you can't get rid of it with a configuration option. And I have to very strongly recommend against using that module for any kind of XML writing for the same reason. XML::Simple's own documentation recommends against its use (see the section "Status of this Module"), and see also XML::Simple needs to go!
I have successfully used XML::Rules as a replacement for XML::Simple several times, for example as I showed here. Once you get into how to configure it, you can produce data structures that look like the output of XML::Simple, but are much more robust (Update: their generation and layout, not the structures themselves). I have noticed that unfortunately the module is not perfect when writing/round-tripping XML files.
Although I haven't worked with it much myself, another module is XML::Compile, which is useful if you have an XML Schema for your XML. The module takes a little bit of setting up, but once it's working, AFAICT so far it seems to be quite reliable.
And in general there are lots of other good XML processing modules, for example XML::LibXML and XML::Twig are two good ones. The above two just help in producing Perl data structures similar to what XML::Simple does.
| [reply] |
|
| [reply] |
Re: hashref with(out) arrays
by bfdi533 (Friar) on Nov 22, 2017 at 18:11 UTC
|
Ok, so taking a stab at XML::Rules and the first thing I find in my XML is a namespace tag prefixed on all of my data elements.
<ns2:Height Units="inches">1.00</ns2:Height>
<ns2:Length Units="inches">1.00</ns2:Length>
<ns2:Width Units="inches">1.00</ns2:Width>
<ns2:Weight Units="pounds">0.01</ns2:Weight>
In my rules, do I refer to this tag as "ns2:Height" or as "Height" somehow accounting for the namespace?
Update:
And do I need to deal with every tag in the XML?
For example, if my XML is:
<ns2:Item>
<ns2:Attributes>
<ns2:Height Units="inches">1.00</ns2:Height>
<ns2:Length Units="inches">1.00</ns2:Length>
<ns2:Width Units="inches">1.00</ns2:Width>
<ns2:Weight Units="pounds">0.01</ns2:Weight>
</ns2:Attributes>
</ns2:Item>
Can I have rules that just deal with Height, Length, Width, Weight, etc? Or do I have to have rules for Item and Attributes to get to the other attributes? I am unclear from reading the XML::Rules documentation. The example has a _default tag which looks like it is supposed to deal with all items not otherwise specified but so far, I am only getting Item in my output as:
0 HASH(0x36a9310)
'Item' => undef
The rules that I am using are:
my @rules_prod = (
_default => sub { $_[0] => $_[1]->{_content}},
'ns2:Weight' => sub { weight => $_[1]->{_content} },
'ns2:Height' => sub { height => $_[1]->{_content} },
);
| [reply] [d/l] [select] |
|
In my rules, do I refer to this tag as "ns2:Height" or as "Height" somehow accounting for the namespace?
You can assign namespace prefixes using the namespaces option, for example namespaces => { 'http://www.example.com/foo' => 'ns2' }, and then use the ns2: prefix in your rules. Or, you can use the same option to map different namespaces to the same prefix, if that is acceptable in your application (!), in the way I showed below (in that case the empty prefix).
Can I have rules that just deal with Height, Length, Width, Weight, etc?
Yes, you can specify multiple tag names for a rule with |. You can also use regexes to specify rules, as in '/foo/' => ... (although I don't think the two can be combined). Here's an example:
use warnings;
use strict;
use XML::Rules;
use Data::Dump;
my $parser = XML::Rules->new(
stripspaces => 3|4, warnoverwrite => 1,
namespaces => {
'http://www.example.com/foo' => '',
'http://www.example.com/bar' => '',
'*' => 'die' }, # don't allow other namespaces
rules => [
'Height|Length|Width|Weight' =>
sub { lc $_[0] => $_[1]->{_content} },
'root' => 'pass',
_default => sub { die "Unknown tag $_[0]" },
] );
dd $parser->parse(<<'END_XML');
<root xmlns="http://www.example.com/bar"
xmlns:foo="http://www.example.com/foo" >
<foo:Height Units="inches">1.00</foo:Height>
<foo:Length Units="inches">1.00</foo:Length>
<foo:Width Units="inches">1.00</foo:Width>
<foo:Weight Units="pounds">0.01</foo:Weight>
</root>
END_XML
__END__
{ height => "1.00", length => "1.00",
weight => 0.01, width => "1.00" }
In this code, I've made the _default rule be to die, that way I can make sure during development that I haven't overlooked a tag in the input XML. But you can of course also specify any _default rule you like.
By the way, since these questions are going a bit off topic from the original post, it might be best to start a new thread on your questions about XML::Rules, showing your code, enough example input to reproduce, etc. (Short, Self-Contained, Correct Example). | [reply] [d/l] [select] |
|
Thank you very much; that is indeed what I needed (and was missing). That example is right on point!
Agreed on the separate issue that would have been better for the XML::Rules. I was really trying to solve the same issue (in my head) so this is, for me, the same ultimate issue on how to get my data out of the XML. The XML::Simple representation of the data structure made the initial question about the array the starting point.
I will keep your recommendations in mind for the future.
| [reply] |
|
|