Beefy Boxes and Bandwidth Generously Provided by pair Networks
Pathologically Eclectic Rubbish Lister
 
PerlMonks  

hashref with(out) arrays

by bfdi533 (Friar)
on Nov 20, 2017 at 22:36 UTC ( [id://1203846]=perlquestion: print w/replies, xml ) Need Help??

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.

Replies are listed 'Best First'.
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"; } }
Re: hashref with(out) arrays
by tangent (Parson) on Nov 21, 2017 at 00:09 UTC
    You could just use ref
    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
Re: hashref with(out) arrays
by virtualsue (Vicar) on Nov 21, 2017 at 10:49 UTC
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);
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!
Re: hashref with(out) arrays
by Anonymous Monk on Nov 20, 2017 at 23:00 UTC
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
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.

      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.

        If you plan to roundtrip and need the result to look like the original (except for the changed data of course) you may have to resort to using the "raw" and "raw extended" rules and/or use a data structure that maps to the right XML. The data structure produced by the module doesn't keep any extra information about the XML it results from and the XML produced from a data structure is the "least complex one that can hold the data". You have to be a bit careful to roundtrip successfully. :-)

        Jenda
        Enoch was right!
        Enjoy the last years of Rome.

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} }, );
      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).

        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.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1203846]
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others rifling through the Monastery: (5)
As of 2024-04-18 06:13 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found