File Coverage

blib/lib/MooX/TO_JSON.pm
Criterion Covered Total %
statement 47 47 100.0
branch 23 28 82.1
condition 8 12 66.6
subroutine 6 6 100.0
pod n/a
total 84 93 90.3


line stmt bran cond sub pod time code
1             package MooX::TO_JSON;
2              
3 1     1   360279 use warnings;
  1         2  
  1         26  
4 1     1   5 use strict;
  1         2  
  1         34  
5              
6             our $VERSION = '0.001';
7             $VERSION = eval $VERSION;
8              
9 1     1   377 use Class::Method::Modifiers qw(install_modifier);
  1         1305  
  1         416  
10              
11             sub import {
12 1     1   7 my ($class) = @_;
13 1         3 my $target = caller;
14              
15 1         15 my @to_json;
16             install_modifier $target, 'fresh', 'TO_JSON', sub {
17 1     1   2165 my $self = shift;
18 1         3 my @structure = ();
19 1         2 foreach my $rule (@to_json) {
20              
21 4 100       10 if($rule->{omit_if_empty}) {
22 1 50       2 next unless $self->${\$rule->{predicate}};
  1         6  
23             }
24              
25 3         3 my $value = $self->${\$rule->{field}};
  3         9  
26 3 100       6 if(my $type = $rule->{type}) {
27 2 50       6 $value = ''+$value if $type == 1;
28 2 100       14 $value = 0+$value if $type == 2;
29 2 50       11 $value = $value ? \1:\0 if $type == 3;
    100          
30             }
31            
32             push @structure, (
33 3         7 $rule->{mapped_field} => $value,
34             );
35             }
36 1 50       7 @structure = $self->modify_json(@structure) if $self->can('modify_json');
37 1         19 return +{ @structure };
38 1         6 };
39              
40 1         162 my %types = (
41             str => 1,
42             num => 2,
43             bool => 3);
44              
45             install_modifier $target, 'around', 'has', sub {
46 5     5   18196 my $orig = shift;
47 5         16 my ($attr, %opts) = @_;
48              
49 5         8 my $json = delete $opts{json};
50 5 100       15 return $orig->($attr, %opts) unless $json;
51              
52 4         7 my $field = $attr;
53 4         6 $field =~ s/^\+//;
54              
55 4 50       9 unless(ref $json) {
56 4 100       9 my $json_to_split = $json eq '1' ? ',':$json;
57 4         14 my ($mapped_field, $extra1, $extra2) = split(',', $json_to_split);
58 4         7 my ($type, $omit_if_empty);
59              
60 4 100 100     17 if(my $found_type = $types{$extra1||''}) {
61 2         4 $type = $found_type;
62             }
63              
64 4   33     20 my $predicate = $opts{predicate} ||= "has_".$field;
65            
66 4 100 100     26 $omit_if_empty = 1 if (($extra1||'') eq 'omit_if_empty') || (($extra2||'') eq 'omit_if_empty');
      50        
      66        
67              
68 4 100       18 $json = +{
69             field => $field,
70             mapped_field => ($mapped_field ? $mapped_field : $field),
71             type => $type,
72             omit_if_empty => $omit_if_empty,
73             predicate => $predicate,
74             };
75             }
76              
77 4         8 push @to_json, $json;
78            
79 4         13 return $orig->($attr, %opts);
80 1         6 };
81             }
82              
83             1;
84              
85             =head1 NAME
86              
87             MooX::TO_JSON - Generate a TO_JSON method from attributes.
88              
89             =head1 SYNOPSIS
90              
91             package Local::User;
92              
93             use Moo;
94             use MooX::TO_JSON;
95              
96             has name => (is=>'ro', json=>1);
97             has age => (is=>'ro', json=>'age-years,num');
98             has alive => (is=>'ro', json=>',bool');
99             has possibly_empty => (is=>'ro', json=>',omit_if_empty');
100             has not_encoded => (is=>'ro');
101              
102             # Optional method to shove in some extra keys
103             sub modify_json {
104             my ($self, %data) = @_;
105             return (%data, extra_stuff => 1);
106             }
107              
108             use JSON::MaybeXS;
109              
110             my $json = JSON::MaybeXS->new(convert_blessed=>1);
111             my $user = Local::User->new(
112             name=>'John',
113             age=>25,
114             alive=>'yes',
115             not_encoded=>'internal');
116              
117             my $encoded = $json->encode($user);
118              
119             The value of C<$encoded> is:
120              
121             {
122             "alive" : true,
123             "name" : "John",
124             "age-years" : 25,
125             "extra_stuff": 1
126             }
127            
128             Please note that the JSON spec does not preserve hash order, so the keys above
129             could be arranged differently.
130              
131             =head1 DESCRIPTION
132              
133             Make it easier to correctly encode your L object into JSON. It does this
134             by inspecting your attributes and injection a C method into your class.
135             You can tag how the attribute will serialize to JSON (forcing it into string or
136             number / boolean).
137              
138             has name => (is=>'ro', json=>1);
139              
140             Setting the C argument to 1 will serialize the attribute value to JSON use
141             the defaults (that is the field name is the same as the attribute name, value is
142             serialized even if C and no value coercions (to number or boolean for example)
143             are forced).
144              
145             has age => (is=>'ro', json=>'age-years,num');
146              
147             Here C will be mapped to 'age-years' and the value forced into number context so
148             that when the JSON encoder touches it the serialized value will be a number not a string.
149              
150             has alive => (is=>'ro', json=>',bool');
151              
152             In this case the value is forced to boolean JSON context.
153              
154             has possibly_empty => (is=>'ro', json=>',omit_if_empty');
155              
156             Lastly if the final tag in the 'json' string is 'omit_if_empty' we will omit including
157             the field in the JSON output IF the attribute is not present (please note C is
158             considered 'present/existing'.) In order to do this we need to set a C arg
159             for the attribute (or use one if you define it). This will slighly pollute the object
160             namespace with the predicate method for each attribute you mark such.
161              
162             Your class can also contain a method C which
163             takes the serialized attributes and allows you to add to them or modify them.
164              
165             =head1 AUTHOR
166              
167             John Napiorkowski (cpan:JJNAPIORK)
168              
169             =head1 COPYRIGHT
170              
171             Copyright (c) 2019 by as listed above.
172              
173             =head1 LICENSE
174              
175             This library is free software and may be distributed under the same terms
176             as perl itself.
177              
178             =cut