File Coverage

blib/lib/MooX/Tag/TO_JSON.pm
Criterion Covered Total %
statement 48 49 97.9
branch 18 20 90.0
condition 10 14 71.4
subroutine 10 10 100.0
pod 1 1 100.0
total 87 94 92.5


line stmt bran cond sub pod time code
1             package MooX::Tag::TO_JSON;
2              
3             # ABSTRACT: Controlled translation of Moo objects into JSON appropriate Hashes
4              
5 8     8   1780868 use v5.10;
  8         84  
6              
7 8     8   52 use strict;
  8         18  
  8         160  
8 8     8   52 use warnings;
  8         19  
  8         378  
9              
10             our $VERSION = '0.05';
11              
12 8     8   3315 use Safe::Isa;
  8         3547  
  8         1125  
13 8     8   1917 use JSON::MaybeXS ();
  8         23490  
  8         199  
14 8     8   3236 use MooX::Tag::TO_HASH::Util ':all';
  8         22  
  8         1316  
15              
16 8     8   64 use Moo::Role;
  8         17  
  8         54  
17             use MooX::TaggedAttributes -propagate,
18             -tags => LC_TO_JSON,
19 8     8   9337 -handler => sub { make_tag_handler( LC_TO_JSON ) };
  8         118446  
  8         96  
  24         160745  
20              
21 8     8   127270 use namespace::clean -except => [ '_tags', '_tag_list' ];
  8         20  
  8         49  
22              
23              
24              
25              
26              
27              
28              
29              
30              
31             sub TO_JSON {
32 15     15 1 112063 my $self = shift;
33              
34 15   50     77 my $to_json = $self->_tags->tag_attr_hash->{ +LC_TO_JSON } // {};
35              
36             # the structure of %to_json is complicated because has() may take
37             # multiple attributes. For example,
38              
39             # has ['foo','bar'] => ( is => 'ro', to_json => '1' );
40              
41             # results in %to_json looking like this:
42              
43             # bar => {
44             # bar => { omit_if_empty => 0, predicate => "has_bar" },
45             # foo => { omit_if_empty => 0, predicate => "has_foo" },
46             # },
47             # foo => {
48             # bar => { omit_if_empty => 0, predicate => "has_bar" },
49             # foo => { omit_if_empty => 0, predicate => "has_foo" },
50             # },
51              
52 15         39140 my %json;
53 15         32 for my $attr ( keys %{$to_json} ) {
  15         64  
54              
55             # TBH, all of this should have been put into a bespoke
56             # generated sub in the tag_handler.
57              
58 131         234 my $opt = $to_json->{$attr}{$attr};
59             # hashes returned by the _tags method are readonly, so need to
60             # check if key exists before querying it to avoid an exception
61             next
62             if exists $opt->{ +IF_EXISTS }
63 131 100 100     317 && !$self->${ \$opt->{ +PREDICATE } };
  30         170  
64              
65             next
66             if exists $opt->{ +IF_DEFINED }
67 117 100 100     312 && !defined $self->${ \$attr };
  6         71  
68              
69             my $name
70             = exists $opt->{ +ALT_NAME }
71 112 100 33     243 ? $opt->{ +ALT_NAME } // $attr
72             : $attr;
73              
74 112         351 my $value = $self->$attr;
75              
76 112 100       212 if ( defined $value ) {
77             # force types
78 98 100       278 if ( exists $opt->{ +BOOL } ) {
    100          
    100          
79 21 50       94 $value = $value ? JSON::MaybeXS::true : JSON::MaybeXS::false;
80             }
81             elsif ( exists $opt->{ +NUM } ) {
82 21         52 $value = 0+ $value;
83             }
84             elsif ( exists $opt->{ +STR } ) {
85 21         55 $value = q{} . $value;
86             }
87             }
88 112         322 $json{$name} = $value;
89             }
90              
91 15 100 66     307 if ( defined( my $mth = $self->can( '_modify_jsonr' ) // $self->can( 'modify_jsonr' ) ) ) {
    50          
92 3         42 $self->$mth( \%json );
93             }
94             elsif ( defined( $mth = $self->can( 'modify_json' ) ) ) {
95 0         0 %json = $self->$mth( %json );
96             }
97              
98 15         273 return \%json;
99             }
100              
101              
102              
103              
104             1;
105              
106             #
107             # This file is part of MooX-Tag-TO_HASH
108             #
109             # This software is Copyright (c) 2022 by Smithsonian Astrophysical Observatory.
110             #
111             # This is free software, licensed under:
112             #
113             # The GNU General Public License, Version 3, June 2007
114             #
115              
116             __END__
117              
118             =pod
119              
120             =for :stopwords Diab Jerius Smithsonian Astrophysical Observatory
121              
122             =head1 NAME
123              
124             MooX::Tag::TO_JSON - Controlled translation of Moo objects into JSON appropriate Hashes
125              
126             =head1 VERSION
127              
128             version 0.05
129              
130             =head1 SYNOPSIS
131              
132             package My::Farm;
133            
134             use Moo;
135             with 'MooX::Tag::TO_JSON';
136            
137             has cow => ( is => 'ro', to_json => 1 );
138             has duck => ( is => 'ro', to_json => 'goose,if_exists', );
139             has horse => ( is => 'ro', to_json => ',if_defined', );
140             has hen => ( is => 'ro', to_json => 1, );
141             has barn_door_closed => ( is => 'ro', to_json => ',bool' );
142             has secret_admirer => ( is => 'ro', );
143            
144             # and somewhere else...
145            
146             use Data::Dumper;
147             my $farm = My::Farm->new(
148             cow => 'Daisy',
149             duck => 'Frank',
150             barn_door_closed => 0,
151             secret_admirer => 'Fluffy',
152             );
153            
154             print Dumper $farm->TO_JSON;
155              
156             # resulting in
157              
158             $VAR1 = {
159             'hen' => undef,
160             'cow' => 'Daisy',
161             'goose' => 'Frank',
162             'barn_door_closed' => bless( do{\(my $o = 0)}, 'JSON::PP::Boolean' )
163             };
164              
165             =head1 DESCRIPTION
166              
167             C<MooX::Tag::TO_JSON> is a L<Moo::Role> which provides a controlled
168             method of converting your L<Moo> based object into a hash appropriate
169             for passing to a JSON encoder. It provides a L<TO_JSON> method which
170             is recognized by most (?) JSON encoders and used to serialize the
171             object.
172              
173             Simply mark each field that should be output with the special option
174             C<to_json> when declaring it:
175              
176             has field => ( is => 'ro', to_json => 1 );
177              
178             and call the L</TO_JSON> method on your instantiated object.
179              
180             my %hash = $obj->TO_JSON;
181              
182             Fields inherited from superclasses or consumed from roles which use
183             C<MooX::Tag::TO_JSON> are automatically handled.
184              
185             If a field's value is another object, L</TO_JSON> will automatically
186             turn that into a hash if it has its own C<TO_JSON> method (you can
187             also prevent that).
188              
189             =head2 Modifying the generated JSON
190              
191             [Originally, this module recommended using a method modifier to the
192             L<TO_JSON> method, this is no longer recommended. See discussion
193             under L<DEPRECATED BEHAVIOR> below.].
194              
195             If the class provides a C<_modify_jsonr> method (or, for backwards
196             capability, C<modify_jsonr>), it will be called as
197              
198             $self->_modify_jsonr( \%json );
199              
200             and should modify the passed hash in place.
201              
202             For compatibility with L<MooX::TO_JSON>, if the class provides a
203             C<modify_json> method it will be called as
204              
205             %json = $self->modify_json( %json );
206              
207             =head2 Usage
208              
209             Add the C<to_json> option to each field which should be
210             included in the json. C<to_json> can either take a value of C<1>,
211             e.g.
212              
213             has field => ( is => 'ro', to_json => 1 );
214              
215             or a string which looks like one of these:
216              
217             alternate_name
218             alternate_name,option_flag,option_flag,...
219             ,option_flag,option_flag,...
220              
221             If C<alternate_name> is specified, that'll be the key used in the
222             output json.
223              
224             C<option_flag> may be one of the following:
225              
226             =over
227              
228             =item C<bool>
229              
230             Force the value into a JSON Boolean context. Compatible with L<MooX::TO_JSON>.
231              
232             =item C<int>
233              
234             Force the value into a JSON numeric context. Compatible with L<MooX::TO_JSON>.
235              
236             =item C<str>
237              
238             Force the value into a JSON numeric context. Compatible with L<MooX::TO_JSON>.
239              
240             =item C<if_exists>
241              
242             Only output the field if it was set. This uses L</Moo>'s attribute
243             predicate (one will be added to the field if it not already
244             specified).
245              
246             It I<will> be output if the field is set to C<undef>.
247              
248             A synonym for this is C<omit_if_empty>, for compatibility with
249             L<MooX::TO_JSON>.
250              
251             =item C<if_defined>
252              
253             Only output the field if it was set and its value is defined.
254              
255             =back
256              
257             =head1 METHODS
258              
259             =head2 TO_JSON
260              
261             %hash = $obj->TO_JSON
262              
263             This method is added to the consuming class or role.
264              
265             =head1 EXAMPLES
266              
267             =head2 Modifying the generated json
268              
269             package My::Test::C4;
270            
271             use Moo;
272             with 'MooX::Tag::TO_JSON';
273            
274             has cow => ( is => 'ro', to_json => 1 );
275             has duck => ( is => 'ro', to_json => 'goose,if_exists', );
276             has horse => ( is => 'ro', to_json => ',if_defined', );
277             has hen => ( is => 'ro', to_json => 1, );
278             has barn_door_closed => ( is => 'ro', to_json => ',bool' );
279             has secret_admirer => ( is => 'ro', );
280            
281             # upper case the json keys
282             sub modify_jsonr {
283             my ( $self, $jsonr ) = @_;
284             $jsonr->{ uc $_ } = delete $jsonr->{$_} for keys %$jsonr;
285             };
286            
287             # and elsewhere:
288             use Data::Dumper;
289            
290             print Dumper(
291             My::Test::C4->new(
292             cow => 'Daisy',
293             hen => 'Ruby',
294             duck => 'Donald',
295             horse => 'Ed',
296             barn_door_closed => 1,
297             secret_admirer => 'Nemo'
298             )->TO_JSON
299             );
300              
301             # resulting in
302              
303             $VAR1 = {
304             'HEN' => 'Ruby',
305             'COW' => 'Daisy',
306             'BARN_DOOR_CLOSED' => bless( do{\(my $o = 1)}, 'JSON::PP::Boolean' ),
307             'GOOSE' => 'Donald',
308             'HORSE' => 'Ed'
309             };
310              
311             =head1 DEPRECATED BEHAVIOR
312              
313             =head2 Using method modifiers to modify the results
314              
315             Previously it was suggested that the C<before> method modifier be used
316             to modify the resultant hash. However, if both a child and parent
317             class consume the C<MooX::Tag::TO_JSON> role and the parent has
318             modified C<TO_JSON>, the parent's modified C<TO_HASH> will not be run;
319             instead the original C<TO_HASH> will. For example,
320              
321             package Role {
322             use Moo::Role;
323             sub foo { print "Role\n" }
324             }
325            
326             package Parent {
327             use Moo;
328             with 'Role';
329             before 'foo' => sub { print "Parent\n" };
330             }
331            
332             package Child {
333             use Moo;
334             extends 'Parent';
335             with 'Role';
336             before 'foo' => sub { print "Child\n" };
337             }
338            
339             Child->new->foo;
340              
341             results in
342              
343             Child
344             Role
345              
346             Note it does not output C<Parent>.
347              
348             =head1 SUPPORT
349              
350             =head2 Bugs
351              
352             Please report any bugs or feature requests to bug-moox-tag-to_hash@rt.cpan.org or through the web interface at: L<https://rt.cpan.org/Public/Dist/Display.html?Name=MooX-Tag-TO_HASH>
353              
354             =head2 Source
355              
356             Source is available at
357              
358             https://gitlab.com/djerius/moox-tag-to_hash
359              
360             and may be cloned from
361              
362             https://gitlab.com/djerius/moox-tag-to_hash.git
363              
364             =head1 SEE ALSO
365              
366             Please see those modules/websites for more information related to this module.
367              
368             =over 4
369              
370             =item *
371              
372             L<MooX::Tag::TO_HASH|MooX::Tag::TO_HASH>
373              
374             =item *
375              
376             L<MooX::Tag::TO_HASH - sibling class to this one.|MooX::Tag::TO_HASH - sibling class to this one.>
377              
378             =item *
379              
380             L<MooX::TO_JSON - this is similar, but doesn't handle fields inherited from super classes or consumed from roles.|MooX::TO_JSON - this is similar, but doesn't handle fields inherited from super classes or consumed from roles.>
381              
382             =back
383              
384             =head1 AUTHOR
385              
386             Diab Jerius <djerius@cpan.org>
387              
388             =head1 COPYRIGHT AND LICENSE
389              
390             This software is Copyright (c) 2022 by Smithsonian Astrophysical Observatory.
391              
392             This is free software, licensed under:
393              
394             The GNU General Public License, Version 3, June 2007
395              
396             =cut