How to instantiate Moose classes from a big hash

时光毁灭记忆、已成空白 提交于 2019-12-22 10:43:38

问题


I have a big hash many levels deep, and I'd like to turn this hash into a set of Moose classes.

The hash looks something like this:

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

And the Moose classes:

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;

Whats the best way to turn this hash into a Company object?

The options I've considered so far are:

  1. Manually loop the %hash, find the deepest "classes" (e.g Person), create these first, then manually add these to the newly created higher level classes (Department), and so on.
  2. Add some kind of coercion functionality to each class, which lets me do something like Company->new(%hash), and make each class create its own "subclasses" (via coercion)
  3. Convert the %hash into a structure similar to what MooseX::Storage would serialize to, then use MooseX::Storage to instatiate everything for me...

Any other ideas or suggestions?


回答1:


You could have a BUILDARGS handler which converts unblessed references in those slots to objects. Coercions is probably the best, but it takes more doing. (Unless this is all coming from a RDBMS, in which case use 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;



回答2:


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.



来源:https://stackoverflow.com/questions/12483775/how-to-instantiate-moose-classes-from-a-big-hash

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!