#!/usr/bin/perl
package MooseX::Role::JSONObject::Util;
use v5.012;
use strict;
use warnings;
use version; our $VERSION = version->declare("v0.1.0");
use List::Util qw/pairfirst/;
use Method::Signatures;
func identify_type(Any $val, Moose::Meta::Attribute $attr)
{
if (!$attr->has_type_constraint) {
if (!defined $val) {
die "MooseX::Role::JSONObject::SKIP\n";
} elsif (ref $val eq 'ARRAY') {
return ('array', '*');
} elsif (ref $val eq 'HASH') {
return ('hash', '*');
}
return ['*'];
}
if (!defined $val && !$attr->is_required) {
die "MooseX::Role::JSONObject::SKIP\n";
}
my $type = $attr->type_constraint;
my @res;
again:
my @handlers = (
'Object' => ['obj', $type->name],
'Num' => ['num'],
'Str' => ['str'],
'Bool' => ['bool'],
'Maybe' => ['maybe', '#PARAM'],
'ArrayRef' => ['array', '#PARAM'],
'HashRef' => ['hash', '#PARAM'],
);
my (undef, $list) = pairfirst { $type->is_a_type_of($a) } @handlers;
if (!defined $list) {
die "FIXME: handle the type constraint for ".$attr->name;
}
push @res, @{$list};
if ($res[$#res] eq '#PARAM') {
pop @res;
$type = $type->{type_parameter};
goto again;
}
# Apparently we're done looping over parameterized type constraints
return @res;
}
func get_value(Any $val, Moose::Meta::Attribute $attr, CodeRef $objfunc)
{
my @type = identify_type($val, $attr);
return get_type_value($val, \@type, $objfunc);
}
func get_type_value(Any $val, ArrayRef[Str] $type, CodeRef $objfunc)
{
my %handlers;
%handlers = (
'obj' => sub {
my ($t, $v) = @_;
my $tname = shift @{$t};
if (@{$t}) {
die "Internal error: ".
"identify_type() returned ".
"extra data after 'obj': @{$t}\n";
}
return $objfunc->($v,
Class::MOP::Class->initialize($tname));
},
'num' => sub {
return $val + 0;
},
'str' => sub {
return $val."";
},
'bool' => sub {
return !!$val;
},
'maybe' => sub {
my ($t, $v) = @_;
return undef unless defined $val;
my $f = shift @{$t};
return $handlers{$f}->($t, $v, $a);
},
'array' => sub {
my ($t, $v) = @_;
return [ map {
get_type_value($_, [@{$t}], $objfunc)
} @{$v} ];
},
'hash' => sub {
my ($t, $v) = @_;
return { map {
($_, get_type_value($v->{$_}, [@{$t}], $objfunc))
} keys %{$v} };
},
);
my $type_first = shift @{$type};
return $handlers{$type_first}->($type, $val);
}
func meta_to_json(Object $obj, Moose::Meta::Class $meta)
{
my @attrs = $meta->get_all_attributes;
my $res = {};
for my $attr (@attrs) {
my $name = $attr->name;
my $hname = $name;
if ($attr->has_applied_traits &&
grep $_ eq 'MooseX::Role::JSONObject::Meta::Trait', @{$attr->applied_traits}) {
$hname = $attr->json_attr;
}
my $v = $obj->{$name};
my $ok;
eval {
$res->{$hname} = get_value($v, $attr, \&meta_to_json);
$ok = 1;
};
my $msg = $@;
if (!$ok && $msg ne "MooseX::Role::JSONObject::SKIP\n") {
die "$msg";
}
}
return $res;
}
func meta_from_json(HashRef $data, Moose::Meta::Class $meta)
{
my @attrs = $meta->get_all_attributes;
my %res;
for my $attr (@attrs) {
my $name = $attr->name;
my $hname = $name;
if ($attr->has_applied_traits &&
grep $_ eq 'MooseX::Role::JSONObject::Meta::Trait', @{$attr->applied_traits}) {
$hname = $attr->json_attr;
}
my $v = $data->{$hname};
my $ok;
eval {
$res{$name} = get_value($v, $attr, \&meta_from_json);
$ok = 1;
};
my $msg = $@;
if (!$ok && $msg ne "MooseX::Role::JSONObject::SKIP\n") {
die "$msg";
}
}
return $meta->new_object(%res);
}
1;
__END__
=encoding utf-8
=head1 NAME
MooseX::Role::JSONObject::Util - helper functions for MooseX::Role::JSONObject
=head1 DESCRIPTION
The C<MooseX::Role::JSONObject::Util> module provides several utility
functions for the C<MooseX::Role::JSONObject> role.
Please note that these functions are only meant for internal use by
C<MooseX::Role::JSONObject> and, as such, any and all of them may
change without prior notice.
=over 4
=item * identify_type()
Examine a C<Moose::Meta::Attribute> object and return a list of
strings describing recursively the attribute's type, e.g.
C<['maybe', 'hash', 'num']> for a C<Maybe[HashRef[Int]]> attribute or
C<['hash', 'array', 'obj', 'Some::Class']> for a
C<HashRef[ArrayRef[Some::Class]]> attribute.
Note that all types descending from C<Num> are represented as C<'num'>.
=item * get_value()
Given an attribute and a function to recurse into objects, parse
the attribute's type using C<identify_type()> and process the given
type's value appropriately. This function is used by both the
C<meta_to_json()> and C<meta_from_json()> functions (see below)
with different functions passed as C<$objfunc>.
=item * get_value_type()
Do the actual work of C<get_value()> after the attribute's type has
been examined by C<identify_type()>.
=item * meta_to_json()
Build a Perl hash suitable for a JSON representation of an object
(or a value) of the given C<Moose::Meta::Class> type.
Uses C<get_value()>, passing a reference to itself as the function to
process complex objects.
=item * meta_from_json()
Build a Moose object (or a simple type's value) of the given
C<Moose::Meta::Class> type, initializing it and its attributes
recursively with the values supplied in the given Perl hash.
Uses C<get_value()>, passing a reference to itself as the function to
process complex objects.
=back
=head1 LICENSE
Copyright (C) 2015 Peter Pentchev E<lt>roam@ringlet.netE<gt>
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.
=head1 AUTHOR
Peter Pentchev E<lt>roam@ringlet.netE<gt>
=cut