File Coverage

blib/lib/Data/Clean/ForJSON.pm
Criterion Covered Total %
statement 33 34 97.0
branch n/a
condition 13 26 50.0
subroutine 8 9 88.8
pod 4 4 100.0
total 58 73 79.4


line stmt bran cond sub pod time code
1             package Data::Clean::ForJSON;
2              
3             our $DATE = '2019-08-08'; # DATE
4             our $VERSION = '0.392'; # VERSION
5              
6 1     1   166413 use 5.010001;
  1         7  
7 1     1   6 use strict;
  1         1  
  1         23  
8 1     1   4 use warnings;
  1         2  
  1         28  
9              
10 1     1   407 use parent qw(Data::Clean);
  1         279  
  1         5  
11              
12 1     1   4973 use Exporter qw(import);
  1         2  
  1         234  
13             our @EXPORT_OK = qw(
14             clean_json_in_place
15             clone_and_clean_json
16             );
17              
18             sub new {
19 2     2 1 3217 my ($class, %opts) = @_;
20              
21             # from FromJSON
22 2   50     15 $opts{"JSON::PP::Boolean"} //= ['one_or_zero'];
23 2   50     12 $opts{"JSON::XS::Boolean"} //= ['one_or_zero']; # this doesn't exist though
24 2   50     11 $opts{"Cpanel::JSON::XS::Boolean"} //= ['one_or_zero']; # this doesn't exist though
25              
26 2   50     10 $opts{DateTime} //= [call_method => 'epoch'];
27 2   50     11 $opts{'Time::Moment'} //= [call_method => 'epoch'];
28 2   50     10 $opts{'Math::BigInt'} //= [call_method => 'bstr'];
29 2   50     13 $opts{Regexp} //= ['stringify'];
30 2   50     11 $opts{version} //= ['stringify'];
31              
32 2   50     10 $opts{SCALAR} //= ['deref_scalar'];
33 2   50     9 $opts{-ref} //= ['replace_with_ref'];
34 2   50     10 $opts{-circular} //= ['clone'];
35 2   50     9 $opts{-obj} //= ['unbless'];
36              
37 2   50     10 $opts{'!recurse_obj'} //= 1;
38 2         17 $class->SUPER::new(%opts);
39             }
40              
41             sub get_cleanser {
42 2     2 1 115 my $class = shift;
43 2         6 state $singleton = $class->new;
44 2         3712 $singleton;
45             }
46              
47             sub clean_json_in_place {
48 1     1 1 12949 __PACKAGE__->get_cleanser->clean_in_place(@_);
49             }
50              
51             sub clone_and_clean_json {
52 0     0 1   __PACKAGE__->get_cleanser->clone_and_clean(@_);
53             }
54              
55             1;
56             # ABSTRACT: Clean data so it is safe to output to JSON
57              
58             __END__
59              
60             =pod
61              
62             =encoding UTF-8
63              
64             =head1 NAME
65              
66             Data::Clean::ForJSON - Clean data so it is safe to output to JSON
67              
68             =head1 VERSION
69              
70             This document describes version 0.392 of Data::Clean::ForJSON (from Perl distribution Data-Clean-ForJSON), released on 2019-08-08.
71              
72             =head1 SYNOPSIS
73              
74             use Data::Clean::ForJSON;
75             my $cleanser = Data::Clean::ForJSON->get_cleanser;
76             my $data = { code=>sub {}, re=>qr/abc/i };
77              
78             my $cleaned;
79              
80             # modifies data in-place
81             $cleaned = $cleanser->clean_in_place($data);
82              
83             # ditto, but deep clone first, return
84             $cleaned = $cleanser->clone_and_clean($data);
85              
86             # now output it
87             use JSON;
88             print encode_json($cleaned); # prints '{"code":"CODE","re":"(?^i:abc)"}'
89              
90             Functional shortcuts:
91              
92             use Data::Clean::ForJSON qw(clean_json_in_place clone_and_clean_json);
93              
94             # equivalent to Data::Clean::ForJSON->get_cleanser->clean_in_place($data)
95             clean_json_in_place($data);
96              
97             # equivalent to Data::Clean::ForJSON->get_cleanser->clone_and_clean($data)
98             $cleaned = clone_and_clean_json($data);
99              
100             =head1 DESCRIPTION
101              
102             This class cleans data from anything that might be problematic when encoding to
103             JSON. This includes coderefs, globs, and so on. Here's what it will do by
104             default:
105              
106             =over
107              
108             =item * Change DateTime and Time::Moment object to its epoch value
109              
110             =item * Change Regexp and version object to its string value
111              
112             =item * Change scalar references (e.g. \1) to its scalar value (e.g. 1)
113              
114             =item * Change other references (non-hash, non-array) to its ref() value (e.g. "GLOB", "CODE")
115              
116             =item * Clone circular references
117              
118             With a default limit of 1, meaning that if a reference is first seen again for
119             the first time, it will be cloned. But if it is seen again for the second time,
120             it will be replaced with "CIRCULAR".
121              
122             To change the default limit, customize your cleanser object:
123              
124             $cleanser = Data::Clean::ForJSON->new(
125             -circular => ["clone", 4],
126             );
127              
128             or you can perform other action for circular references, see L<Data::Clean> for
129             more details.
130              
131             =item * Unbless other types of objects
132              
133             =back
134              
135             Cleaning recurses into objects.
136              
137             Data that has been cleaned will probably not be convertible back to the
138             original, due to information loss (for example, coderefs converted to string
139             C<"CODE">).
140              
141             The design goals are good performance, good defaults, and just enough
142             flexibility. The original use-case is for returning JSON response in HTTP API
143             service.
144              
145             This module is significantly faster than modules like L<Data::Rmap> or
146             L<Data::Visitor::Callback> because with something like Data::Rmap you repeatedly
147             invoke callback for each data item. This module, on the other hand, generates a
148             cleanser code using eval(), using native Perl for() loops.
149              
150             If C<LOG_CLEANSER_CODE> environment is set to true, the generated cleanser code
151             will be logged using L<Log::get> at trace level. You can see it, e.g. using
152             L<Log::ger::Output::Screen>:
153              
154             % LOG_CLEANSER_CODE=1 perl -MLog::ger::Output=Screen -MLog::ger::Level::trace -MData::Clean::ForJSON \
155             -e'$c=Data::Clean::ForJSON->new; ...'
156              
157             =head1 FUNCTIONS
158              
159             None of the functions are exported by default.
160              
161             =head2 clean_json_in_place($data)
162              
163             A shortcut for:
164              
165             Data::Clean::ForJSON->get_cleanser->clean_in_place($data)
166              
167             =head2 clone_and_clean_json($data) => $cleaned
168              
169             A shortcut for:
170              
171             $cleaned = Data::Clean::ForJSON->get_cleanser->clone_and_clean($data)
172              
173             =head1 METHODS
174              
175             =head2 CLASS->get_cleanser => $obj
176              
177             Return a singleton instance, with default options. Use C<new()> if you want to
178             customize options.
179              
180             =head2 CLASS->new() => $obj
181              
182             Create a new instance.
183              
184             =head2 $obj->clean_in_place($data) => $cleaned
185              
186             Clean $data. Modify data in-place.
187              
188             =head2 $obj->clone_and_clean($data) => $cleaned
189              
190             Clean $data. Clone $data first.
191              
192             =head1 FAQ
193              
194             =head2 Why clone/modify? Why not directly output JSON?
195              
196             So that the data can be used for other stuffs, like outputting to YAML, etc.
197              
198             =head2 Why is it slow?
199              
200             If you use C<new()> instead of C<get_cleanser()>, make sure that you do not
201             construct the Data::Clean::ForJSON object repeatedly, as the constructor
202             generates the cleanser code first using eval(). A short benchmark (run on my
203             slow Atom netbook):
204              
205             % bench -MData::Clean::ForJSON -b'$c=Data::Clean::ForJSON->new' \
206             'Data::Clean::ForJSON->new->clone_and_clean([1..100])' \
207             '$c->clone_and_clean([1..100])'
208             Benchmarking sub { Data::Clean::ForJSON->new->clean_in_place([1..100]) }, sub { $c->clean_in_place([1..100]) } ...
209             a: 302 calls (291.3/s), 1.037s (3.433ms/call)
210             b: 7043 calls (4996/s), 1.410s (0.200ms/call)
211             Fastest is b (17.15x a)
212              
213             Second, you can turn off some checks if you are sure you will not be getting bad
214             data. For example, if you know that your input will not contain circular
215             references, you can turn off circular detection:
216              
217             $cleanser = Data::Clean::ForJSON->new(-circular => 0);
218              
219             Benchmark:
220              
221             $ perl -MData::Clean::ForJSON -MBench -E '
222             $data = [[1],[2],[3],[4],[5]];
223             bench {
224             circ => sub { state $c = Data::Clean::ForJSON->new; $c->clone_and_clean($data) },
225             nocirc => sub { state $c = Data::Clean::ForJSON->new(-circular=>0); $c->clone_and_clean($data) }
226             }, -1'
227             circ: 9456 calls (9425/s), 1.003s (0.106ms/call)
228             nocirc: 13161 calls (12885/s), 1.021s (0.0776ms/call)
229             Fastest is nocirc (1.367x circ)
230              
231             The less number of checks you do, the faster the cleansing process will be.
232              
233             =head2 Why am I getting 'Not a CODE reference at lib/Data/Clean.pm line xxx'?
234              
235             [2013-08-07 ] This error message is from Data::Clone::clone() when it is cloning
236             an object. If you are cleaning objects, instead of using clone_and_clean(), try
237             using clean_in_place(). Or, clone your data first using something else like
238             L<Sereal>.
239              
240             =head1 ENVIRONMENT
241              
242             LOG_CLEANSER_CODE
243              
244             =head1 HOMEPAGE
245              
246             Please visit the project's homepage at L<https://metacpan.org/release/Data-Clean-ForJSON>.
247              
248             =head1 SOURCE
249              
250             Source repository is at L<https://github.com/perlancar/perl-Data-Clean-ForJSON>.
251              
252             =head1 BUGS
253              
254             Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Data-Clean-ForJSON>
255              
256             When submitting a bug or request, please include a test-file or a
257             patch to an existing test-file that illustrates the bug or desired
258             feature.
259              
260             =head1 SEE ALSO
261              
262             L<Data::Rmap>
263              
264             L<Data::Visitor::Callback>
265              
266             L<Data::Abridge> is similar in goal, which is to let Perl data structures (which
267             might contain stuffs unsupported in JSON) be encodeable to JSON. But unlike
268             Data::Clean::ForJSON, it has some (currently) non-configurable rules, like
269             changing a coderef with a hash C<< {CODE=>'\&main::__ANON__'} >> or a scalar ref
270             with C<< {SCALAR=>'value'} >> and so on. Note that the abridging process is
271             similarly unidirectional (you cannot convert back the original Perl data
272             structure).
273              
274             =head1 AUTHOR
275              
276             perlancar <perlancar@cpan.org>
277              
278             =head1 COPYRIGHT AND LICENSE
279              
280             This software is copyright (c) 2019, 2018, 2017, 2016, 2015, 2014, 2013, 2012 by perlancar@cpan.org.
281              
282             This is free software; you can redistribute it and/or modify it under
283             the same terms as the Perl 5 programming language system itself.
284              
285             =cut