Search code examples
jsonperlintrospectionmoose

Determine the Moose Type for providing conversion to JSON


I have a class, MyClass:

package MyClass;
use Moose;

has 'IntegerMember' => (
    is => 'rw',
    isa => 'Int'
);

has 'BooleanMember' => (
    is => 'rw',
    isa => 'Bool'
);

sub TO_JSON {
    my $self = shift;
    return { % { $self } };
}

Currently when I instantiate MyClass and pass the new object to the json_encoder I get a JSON string returned as expected. I was hoping that the perl booleans ( 1,0 ) would be converted to ( true, false ) but that is not how the JSON module is designed:

use JSON;
use MyClass;

my $object_to_encode = MyClass->new ( 
    IntegerMember => 10,
    BooleanMember => 1
);
my $json_encoder = JSON->new->convert_blessed;
my $json_data = $json_encoder->encode( $object_to_encode );

In MyClass, I want to improve my TO_JSON subroutine to provide a conversion of any Moose 'Bool' member from ( 1 or 0 ) to ( true or false ):

sub TO_JSON {
    my $self = shift;
    for my $member ( %$self ) {
        if {
            # Convert '1' to 'true' if Moose Type is 'Bool'
        } else {
            # Keep the member as is
        }
    }
}

How can I determine the Moose Type as I iterate through MyClass' members so I can provide a mechanism for the conversion?


Solution

  • Here's one way to do it:

    package MyClass;
    use Moose;
    
    has 'IntegerMember' => (
        is => 'rw',
        isa => 'Int'
    );
    
    has 'BooleanMember' => (
        is => 'rw',
        isa => 'Bool'
    );
    
    sub TO_JSON {
        my $self = shift;
        my $meta = $self->meta;
        my $result = {};
        for my $attr ($meta->get_all_attributes) {
            my $name = $attr->name;
            my $value = $attr->get_value($self);
            my $type = $attr->type_constraint;
            if ($type && $type->equals('Bool')) {
                $value = $value ? \1 : \ 0;
            }
            $result->{$name} = $value;
        }
        return $result;
    }
    
    1
    

    We use the metaclass object (accessible via ->meta) to introspect the class and get a list of all attributes (in the form of meta-attribute objects).

    For each attribute, we get the name, current value, and type constraint (if any). If the type is Bool, we convert the value to either \1 or \0. The JSON module understands these values and converts them to true or false.

    And a test program:

    use strict;
    use warnings;
    use JSON::MaybeXS;
    use MyClass;
    
    my $object_to_encode = MyClass->new ( 
        IntegerMember => 10,
        BooleanMember => 1
    );
    my $json_encoder = JSON->new->convert_blessed;
    my $json_data = $json_encoder->encode( $object_to_encode );
    
    print "Result: $json_data\n";
    

    Output:

    Result: {"IntegerMember":10,"BooleanMember":true}