3

我有一个很多层次的大哈希,我想把这个哈希变成一组 Moose 类。

哈希看起来像这样:

my %hash = (
    company => {
        id => 1,
        name => 'CorpInc',
        departments => [
            {
                id => 1,
                name => 'Sales',
                employees => [
                    {
                        id => 1,
                        name => 'John Smith',
                        age => '30',
                    },
                ],
            },
            {
                id => 2,
                name => 'IT',
                employees => [
                    {
                        id => 2,
                        name => 'Lucy Jones',
                        age => '28',
                    },
                    {
                        id => 3,
                        name => 'Miguel Cerveza',
                        age => '25',
                    },
                ],
            },
        ],
    }
);

还有驼鹿类:

package Company;
use Moose;

has 'id'   => (is => 'ro', isa => 'Num');
has 'name' => (is => 'ro', isa => 'Str');
has 'departments' => (is => 'ro', isa => 'ArrayRef[Company::Department]');
1;

package Company::Department;
use Moose;

has 'id'   => (is => 'ro', isa => 'Num');
has 'name' => (is => 'ro', isa => 'Str');
has 'employees' => (is => 'ro', isa => 'ArrayRef[Company::Person]');
1;

package Company::Person;
use Moose;

has 'id'         => (is => 'ro', isa => 'Num');
has 'first_name' => (is => 'ro', isa => 'Str');
has 'last_name'  => (is => 'ro', isa => 'Str');
has 'age'        => (is => 'ro', isa => 'Num');
1;

将此哈希转换为 Company 对象的最佳方法是什么?

到目前为止我考虑过的选项是:

  1. 手动循环 %hash,找到最深的“类”(例如 Person),首先创建这些,然后手动将它们添加到新创建的更高级别的类(部门)中,依此类推。
  2. 为每个类添加某种强制功能,这让我可以执行 Company->new(%hash) 之类的操作,并使每个类创建自己的“子类”(通过强制)
  3. 将 %hash 转换为类似于 MooseX::Storage 序列化的结构,然后使用 MooseX::Storage 为我实例化所有内容......

还有其他想法或建议吗?

4

2 回答 2

2

您可以有一个BUILDARGS处理程序,它将这些插槽中的未受祝福的引用转换为对象。强制可能是最好的,但它需要做更多的事情。(除非这一切都来自 RDBMS,在这种情况下使用DBIx::Class)。

#!/usr/bin/env perl

use warnings;
use strict;

package Company;
use Moose;

has 'id'   => (is => 'ro', isa => 'Num');
has 'name' => (is => 'ro', isa => 'Str');
has 'departments' => (is => 'ro', isa => 'ArrayRef[Company::Department]');

sub BUILDARGS {
  my $self = shift;
  my $args = $self->SUPER::BUILDARGS(@_);
  @{ $args->{departments} } = 
    map { eval{ $_->isa('Company::Department') } ? $_ : Company::Department->new($_) }
    @{ $args->{departments} };

  return $args;
};

package Company::Department;
use Moose;

has 'id'   => (is => 'ro', isa => 'Num');
has 'name' => (is => 'ro', isa => 'Str');
has 'employees' => (is => 'ro', isa => 'ArrayRef[Company::Person]');

sub BUILDARGS {
  my $self = shift;
  my $args = $self->SUPER::BUILDARGS(@_);
  @{ $args->{employees} } = 
    map { eval{ $_->isa('Company::Person') } ? $_ : Company::Person->new($_) }
    @{ $args->{employees} };

  return $args;
};

package Company::Person;
use Moose;

has 'id'         => (is => 'ro', isa => 'Num');
has 'name'       => (is => 'ro', isa => 'Str');
has 'age'        => (is => 'ro', isa => 'Num');

package main;

my %hash = (
    company => {
        id => 1,
        name => 'CorpInc',
        departments => [
            {
                id => 1,
                name => 'Sales',
                employees => [
                    {
                        id => 1,
                        name => 'John Smith',
                        age => '30',
                    },
                ],
            },
            {
                id => 2,
                name => 'IT',
                employees => [
                    {
                        id => 2,
                        name => 'Lucy Jones',
                        age => '28',
                    },
                    {
                        id => 3,
                        name => 'Miguel Cerveza',
                        age => '25',
                    },
                ],
            },
        ],
    }
);

my $company = Company->new($hash{company});
use Data::Dumper;
print Dumper $company;
于 2012-09-18T21:05:23.107 回答
1

I've used your option 2 several times and it worked fine for me. Last instance was inflating JIRA REST API results into real objects. Note that with coercions you can also lookup an existing instance by id and create only if it does not exist.

Edit: Here is some code to demonstrate those coercions:

package Company::Types;
use Moose::Util::TypeConstraints;

subtype 'Company::Departments', as 'ArrayRef[Company::Department]';
coerce  'Company::Departments', from 'ArrayRef', via {
    require Company::Department;
    [ map { Company::Department->new($_) } @$_ ]
};

subtype 'Company::Persons', as 'ArrayRef[Company::Person]';
coerce  'Company::Persons', from 'ArrayRef', via {
    require Company::Person;
    [ map { Company::Person->new($_) } @$_ ]
};

no Moose::Util::TypeConstraints;

and in those classes:

use Company::Types;

has 'departments' => (is => 'ro', isa => 'Company::Departments', coerce => 1);
has 'employees'   => (is => 'ro', isa => 'Company::Persons', coerce => 1);

then you can pass whole structure into Company constructor and all gets inflated properly.

于 2012-09-19T17:48:40.680 回答