YAML confusion with a List of Hashes

barbie on 2007-02-28T11:56:43

The following is a snippet from a valid META.yml file:

   optional_features:
     - foo:
       description:        test
       requires_os:        Linux
       requires:
         Test::More:       0.47

and below is the snippet of data that both YAML::LoadFile() and YAML::Syck::LoadFile() return:

'optional_features' => 
  [
    {
      'foo'               => undef,
      'description'       => 'test',
      'requires_os'       => 'Linux',
      'requires'          => { 'Test::More' => '0.47' },
   }
  ],

Note that 'foo' is inside the hash, not the first item in the list. To me this is a design flaw, as it makes no sense to me having the list identifier as part of the hash. Plus it makes it painfully difficult to validate. I would have expected the following:

'optional_features' => 
  [
    'foo' => {
      'description'       => 'test',
      'requires_os'       => 'Linux',
      'requires'          => { 'Test::More' => '0.47' },
   }
  ],

Looking through the documents and bug reports I can't see anything that matches this, so I'm not sure whether I've misunderstood the specs or whether I have actually found a problem with the way YAML interprets this particular type of data structure. At the moment I don't have enough knowledge of YAML or YAML::Syck to understand how this is being parsed. Does anyone who knows them better than I, know whether this is a bug or expected behaviour?


Nesting in YAML

ferreira on 2007-02-28T12:45:59

The nesting of data structures in YAML blocks is indicated by indenting. So that:

optional_features:
  - foo:
    description:        test
    requires_os:        Linux
    requires:
      Test::More:       0.47
is just a tight version of

optional_features:
  -
    foo:
    description:        test
    requires_os:        Linux
    requires:
      Test::More:       0.47
In contrast,

optional_features:
  - foo:
      description:        test
      requires_os:        Linux
      requires:
        Test::More:       0.47
turns into

'optional_features' =>
  [ { foo =>
        {
          'description'       => 'test',
          'requires_os'       => 'Linux',
          'requires'          => { 'Test::More' => '0.47' },
       }
   }
  ],
which is closer to what you're looking for (I guess).

Re:Nesting in YAML

barbie on 2007-02-28T14:38:33

Yep, that is more what I was after. I'd misunderstood the bit about indenting, as I'd indented from the list, not the map key. A new version of Test-YAML-Meta coming soon :)

Thanks.

Re:Nesting in YAML

izut on 2007-02-28T15:19:00

You were almost there :-)

Actually, you don't need the '-':

#!env perl

use strict;
use strict;
use warnings;

use Data::Dumper;
use YAML qw(Load);

my $s = Load(<<EOF);
---
optional_features:
  foo:
    description:        test
    requires_os:        Linux
    requires:
      Test::More:       0.47
  bar:
    description:        test

EOF

print Dumper $s;
The result is this:

$VAR1 = {
          'optional_features' => {
                                   'bar' => {
                                              'description' => 'test'
                                            },
                                   'foo' => {
                                              'requires_os' => 'Linux',
                                              'requires' => {
                                                              'Test::More' => '0.47'
                                                            },
                                              'description' => 'test'
                                            }
                                 }
        };

Re:Nesting in YAML

barbie on 2007-02-28T15:38:23

I was interpreting the current META.yml Specification (it's hidden within the Recommends section, as I think the heading has been accidentally omitted). I'd just got the indentation wrong :)

this is what you want

arakan on 2007-02-28T14:51:25

I think you have misunderstood the specs because you are getting the expected behaviour.

You are interested in the value of the "optional_features" key, and you expect that to be a ref to a list of two elements: the first element is "foo"; the second is a ref to a hash of the other data.

When you describe what you want you can see that the list has 2 elements. So in YAML you need one "-" for each element. So what you want is:

    optional_features:
        - foo
        - description: test
            requires_os: Linux
            requires:
                Test::More: 0.47

Re:this is what you want

arakan on 2007-02-28T15:03:57

Actually, you need to *correct the indentation*.  (Why does this site screw with my indentation in plain text?  Yes, I should use "preview".  I'm now using "code")

*This* is what you want:

optional_features:
  - foo
  - description: test
    requires_os: Linux
    requires:
      Test::More: 0.47

Re:this is what you want

barbie on 2007-02-28T15:33:29

Except it isn't ;)

optional_features is a list, with each element of the list being a map with an identifier. The indentation I'd used made it look like the list element had no identifier, hence why 'foo => undef' existed inside the map, when I meant for it be the identifier for the map. ferreira has put me on the right track.

As I suspected, I had just misunderstood the specs, as I didn't think I could have been the first person to come across this.

As for indenting in use.perl, you want '<ecode>' tags not '<code>' tags :)

Re:this is what you want

arakan on 2007-02-28T16:29:07

Are you sure it isn't what you want?  I'm trying to understand what you are describing.

You say "each element of the list being a map with an identifier".  That is possible in Perl6, but in Perl5 a list (or array) element can only be a simple scalar or a reference to something.

So I think you must mean that you want a list that looks something like this:  [ foo => { ... } ]

Is that what you want?

That list has 2 elements: the first is "foo"; the second is a hashref.

In your initial post you said you expected

'optional_features' =>
  [
    'foo' => {
      'description'       => 'test',
      'requires_os'       => 'Linux',
      'requires'          => { 'Test::More' => '0.47' },
   }
  ],

as your resulting data structure.  That is exactly what is produced by my YAML:

optional_features:
  - foo
  - description: test
    requires_os: Linux
    requires:
      Test::More: 0.47

Try it.