saintmike has asked for the wisdom of the Perl Monks concerning the following question:

Esteemed monks,

given a textual data structure description, I'd like to set up a nested Perl data structure, consisting of hashes and arrays.

The description looks like this:

"foo[1].bar.baz[0]", value: 123
and should be transformed into
$data = { foo => [ undef, { bar => { baz => ['123'] } } ] }

I've come up with a solution (don't ask how long it took :), but I keep wondering: Doesn't there have to be a better way? Sure, you could modify the description a bit and then eval it, but I don't like using eval.

Here's my clunky code:

use Data::Dumper; my $data = str2data('foo[1].bar.baz[0]', 123); print Dumper($data), "\n"; sub str2data { my($string, $value) = @_; my $tmp; my @idxs = (); for my $part (split /\./, $string) { if($part =~ /(.*?)\[(.*?)\]/) { push @idxs, $1, $2; } else { push @idxs, $part; } } while(defined(my $idx = pop @idxs)) { if($idx =~ /^\d+$/) { $tmp = []; $tmp->[$idx] = $value; $value = $tmp; } else { $tmp = {}; $tmp->{$idx} = $value; $value = $tmp; } } return $value; }

Can you do better?

Replies are listed 'Best First'.
Re: Construct a data structure from a string
by BrowserUk (Patriarch) on Sep 23, 2004 at 02:57 UTC
    #! perl -slw use strict; use Data::Dumper; my $string = "foo[1].bar.baz[0] = 123"; my @bits = split '\.| = ', $string; my $data = pop @bits; m/(\w+)(?:\[(\d+)\])?/ and $data = { $1 => $2 ? [ (undef) x $2, $data ] : $data } for reverse @bits; print Dumper $data; __END__ P:\test>393104 $VAR1 = { 'foo' => [ undef, { 'bar' => { 'baz' => '123' } } ] };

    Examine what is said, not who speaks.
    "Efficiency is intelligent laziness." -David Dunham
    "Think for yourself!" - Abigail
    "Memory, processor, disk in that order on the hardware side. Algorithm, algorithm, algorithm on the code side." - tachyon
Re: Construct a data structure from a string
by jweed (Chaplain) on Sep 23, 2004 at 03:26 UTC
    Not quite as sexy as BrowserUK's (though a bit clearer to me): here's my str2data.
    sub str2data { my($string, $value) = @_; my $tmp; for my $part (reverse split /\./, $string) { undef $tmp; if ($part =~ /([^][]*?)\[(\d*?)\]/) { $tmp->{$1}[$2] = $value; $value = $tmp; } else { $tmp->{$part} = $value; $value = $tmp; } } return $value; }
    It's single pass, but uses a tmp variable, which is somewhat clunky. Oh well.



    Code is (almost) always untested.
    http://www.justicepoetic.net/
Re: Construct a data structure from a string
by dws (Chancellor) on Sep 23, 2004 at 03:33 UTC

    given a textual data structure description, I'd like to set up a nested Perl data structure, consisting of hashes and arrays.

    As an aside, what you're doing looks very much what like the J2EE world (and now the Ruby on Rails framework) do to reconstitute object hierarchies from flat data in form posts. Perhaps that's what you intend this for.

    What you've got looks like a decent start, though it can be confused by invalid input. E.g.,

    foo.bar[baz].47

    A few small adjustments can compensate.

Re: Construct a data structure from a string
by nobull (Friar) on Sep 23, 2004 at 06:39 UTC
    This is just a slightly more complex version of the "convert a list to a hierachical hash subscript" question.
    use strict; use warnings; use Data::Dumper; my $data = str2data('foo[1].bar.baz[0]', 123); print Dumper($data), "\n"; sub str2data { my($string, $value) = @_; my $r = \my $return; $r = $1 ? \$$r->{$1} : \$$r->[$2] while $string =~ /\G(?:\.?(\w+)|\[(\d+)\])/g; $$r = $value; $return; }
    Note - I'm assuming that the input is known to be valid and that $1 can never be '0'. If $1 could ever be '0' then insert 'defined' in the obvious place.
      $r = $1 ? \$$r->{$1} : \$$r->[$2] while $string =~ /\G(?:\.?(\w+)|\[(\d+)\])/g;

      Note - I'm assuming that the input is known to be valid and that $1 can never be '0'.

      Given I'm already assuming $string is well formed the regex is much more complex than it needs to be.

      $r = $1 ? \$$r->{$1} : \$$r->[$2] while $string =~ /(\w+)|\[(\d+)/g;
Re: Construct a data structure from a string
by alftheo (Scribe) on Sep 23, 2004 at 10:53 UTC
    You could use YAML, which would give you
    use strict; use Data::Dumper; use YAML; my $ref = Load(<<'XXX'); --- foo: - ~ - bar: baz: - 123 XXX print Dumper($ref);
Re: Construct a data structure from a string
by TedPride (Priest) on Sep 23, 2004 at 08:27 UTC
    $inp = '"foo[1].bar.baz[0]", value: 123'; $inp =~ /^"(.*?)", value: (.*)$/; my $layout = $1; my $data = $2; while ($layout =~ s/(\[(\d+)\]|\.?(\w+))$//) { if ($3) { $data = { $3 => $data }; } else { $data = [ (undef) x $2, $data ]; } } print $data->{'foo'}->[1]->{'bar'}->{'baz'}->[0];
Re: Construct a data structure from a string
by water (Deacon) on Sep 24, 2004 at 02:25 UTC
    Use YAML.

    Doing such things by hand is too tedious and too easy to screw up -- resembles parsing CGI form data w/o CGI.pm.

    YAML++