File Coverage

blib/lib/Mojo/JSON.pm
Criterion Covered Total %
statement 130 131 99.2
branch 85 86 98.8
condition 14 14 100.0
subroutine 30 30 100.0
pod 5 5 100.0
total 264 266 99.2


line stmt bran cond sub pod time code
1             package Mojo::JSON;
2 75     75   222201 use Mojo::Base -strict;
  75         394  
  75         654  
3              
4 75     75   591 use Carp qw(croak);
  75         188  
  75         8742  
5 75     75   586 use Exporter qw(import);
  75         207  
  75         2747  
6 75     75   60769 use JSON::PP ();
  75         1470924  
  75         3678  
7 75     75   2161 use Mojo::Util qw(decode encode monkey_patch);
  75         179  
  75         7251  
8 75     75   538 use overload ();
  75         833  
  75         1971  
9 75     75   615 use Scalar::Util qw(blessed);
  75         159  
  75         10196  
10              
11             # For better performance Cpanel::JSON::XS is required
12             use constant JSON_XS => $ENV{MOJO_NO_JSON_XS}
13             ? 0
14 75 100   75   531 : !!eval { require Cpanel::JSON::XS; Cpanel::JSON::XS->VERSION('4.20'); 1 };
  75         155  
  75         2045  
  74         90528  
  74         363718  
  74         7881  
15              
16 75     75   724 use constant CORE_BOOLS => defined &builtin::is_bool;
  75         159  
  75         7219  
17              
18             BEGIN {
19 75     75   264174 warnings->unimport('experimental::builtin') if CORE_BOOLS;
20             }
21              
22             our @EXPORT_OK = qw(decode_json encode_json false from_json j to_json true);
23              
24             # Escaped special character map
25             my %ESCAPE
26             = ('"' => '"', '\\' => '\\', '/' => '/', 'b' => "\x08", 'f' => "\x0c", 'n' => "\x0a", 'r' => "\x0d", 't' => "\x09");
27             my %REVERSE = map { $ESCAPE{$_} => "\\$_" } keys %ESCAPE;
28             for (0x00 .. 0x1f) { $REVERSE{pack 'C', $_} //= sprintf '\u%.4X', $_ }
29              
30             # Replace pure-Perl fallbacks if Cpanel::JSON::XS is available
31             if (JSON_XS) {
32             my $BINARY = Cpanel::JSON::XS->new->utf8;
33             my $TEXT = Cpanel::JSON::XS->new;
34             $_->canonical->allow_nonref->allow_unknown->allow_blessed->convert_blessed->stringify_infnan->escape_slash
35             ->allow_dupkeys
36             for $BINARY, $TEXT;
37 147     147   32613 monkey_patch __PACKAGE__, 'encode_json', sub { $BINARY->encode($_[0]) };
38 137     137   14174 monkey_patch __PACKAGE__, 'decode_json', sub { $BINARY->decode($_[0]) };
39 2     2   843 monkey_patch __PACKAGE__, 'to_json', sub { $TEXT->encode($_[0]) };
40 10     10   1471 monkey_patch __PACKAGE__, 'from_json', sub { $TEXT->decode($_[0]) };
41             }
42              
43             sub decode_json {
44 68     68 1 69685 my $err = _decode(\my $value, shift);
45 68 100       2569 return defined $err ? croak $err : $value;
46             }
47              
48 60     60 1 81015 sub encode_json { encode('UTF-8', _encode_value(shift)) }
49              
50 16     16 1 3305 sub false () {JSON::PP::false}
51              
52             sub from_json {
53             my $err = _decode(\my $value, shift, 1);
54             return defined $err ? croak $err : $value;
55             }
56              
57             sub j {
58 99 100 100 99 1 20850 return encode_json($_[0]) if ref $_[0] eq 'ARRAY' || ref $_[0] eq 'HASH';
59 95         524 return scalar eval { decode_json($_[0]) };
  92         414  
60             }
61              
62             sub to_json { _encode_value(shift) }
63              
64 21     21 1 19082 sub true () {JSON::PP::true}
65              
66             sub _decode {
67 71     71   127 my $valueref = shift;
68              
69 71 100       135 eval {
70              
71             # Missing input
72 71 100       225 die "Missing or empty input at offset 0\n" unless length(local $_ = shift);
73              
74             # UTF-8
75 70 100       237 $_ = decode('UTF-8', $_) unless shift;
76 70 100       153 die "Input is not UTF-8 encoded\n" unless defined;
77              
78             # Value
79 68         143 $$valueref = _decode_value();
80              
81             # Leftover data
82 55 100       344 /\G[\x20\x09\x0a\x0d]*\z/gc or _throw('Unexpected data');
83             } ? return undef : chomp $@;
84              
85 21         51 return $@;
86             }
87              
88             sub _decode_array {
89 53     53   70 my @array;
90 53         560 until (m/\G[\x20\x09\x0a\x0d]*\]/gc) {
91              
92             # Value
93 75         135 push @array, _decode_value();
94              
95             # Separator
96 70 100       218 redo if /\G[\x20\x09\x0a\x0d]*,/gc;
97              
98             # End
99 44 100       160 last if /\G[\x20\x09\x0a\x0d]*\]/gc;
100              
101             # Invalid character
102 2         6 _throw('Expected comma or right square bracket while parsing array');
103             }
104              
105 46         89 return \@array;
106             }
107              
108             sub _decode_object {
109 16     16   32 my %hash;
110 16         82 until (m/\G[\x20\x09\x0a\x0d]*\}/gc) {
111              
112             # Quote
113 20 100       62 /\G[\x20\x09\x0a\x0d]*"/gc or _throw('Expected string while parsing object');
114              
115             # Key
116 17         26 my $key = _decode_string();
117              
118             # Colon
119 17 100       113 /\G[\x20\x09\x0a\x0d]*:/gc or _throw('Expected colon while parsing object');
120              
121             # Value
122 16         30 $hash{$key} = _decode_value();
123              
124             # Separator
125 16 100       39 redo if /\G[\x20\x09\x0a\x0d]*,/gc;
126              
127             # End
128 10 100       34 last if /\G[\x20\x09\x0a\x0d]*\}/gc;
129              
130             # Invalid character
131 1         5 _throw('Expected comma or right curly bracket while parsing object');
132             }
133              
134 11         28 return \%hash;
135             }
136              
137             sub _decode_string {
138 64     64   179 my $pos = pos;
139              
140             # Extract string with escaped characters
141 64         13385 m!\G((?:(?:[^\x00-\x1f\\"]|\\(?:["\\/bfnrt]|u[0-9a-fA-F]{4})){0,32766})*)!gc;
142 64         163 my $str = $1;
143              
144             # Invalid character
145 64 100       162 unless (m/\G"/gc) {
146 2 100       30 _throw('Unexpected character or invalid escape while parsing string') if /\G[\x00-\x1f\\]/;
147 1         4 _throw('Unterminated string');
148             }
149              
150             # Unescape popular characters
151 62 100       178 if (index($str, '\\u') < 0) {
152 57         145 $str =~ s!\\(["\\/bfnrt])!$ESCAPE{$1}!gs;
153 57         185 return $str;
154             }
155              
156             # Unescape everything else
157 5         10 my $buffer = '';
158 5         26 while ($str =~ /\G([^\\]*)\\(?:([^u])|u(.{4}))/gc) {
159 10         19 $buffer .= $1;
160              
161             # Popular character
162 10 100       19 if ($2) { $buffer .= $ESCAPE{$2} }
  4         10  
163              
164             # Escaped
165             else {
166 6         20 my $ord = hex $3;
167              
168             # Surrogate pair
169 6 100       16 if (($ord & 0xf800) == 0xd800) {
170              
171             # High surrogate
172 3 100       14 ($ord & 0xfc00) == 0xd800 or pos = $pos + pos($str), _throw('Missing high-surrogate');
173              
174             # Low surrogate
175 2 100       13 $str =~ /\G\\u([Dd][C-Fc-f]..)/gc or pos = $pos + pos($str), _throw('Missing low-surrogate');
176              
177 1         4 $ord = 0x10000 + ($ord - 0xd800) * 0x400 + (hex($1) - 0xdc00);
178             }
179              
180             # Character
181 4         21 $buffer .= pack 'U', $ord;
182             }
183             }
184              
185             # The rest
186 3         16 return $buffer . substr $str, pos($str), length($str);
187             }
188              
189             sub _decode_value {
190              
191             # Leading whitespace
192 159     159   459 /\G[\x20\x09\x0a\x0d]*/gc;
193              
194             # String
195 159 100       382 return _decode_string() if /\G"/gc;
196              
197             # Object
198 112 100       282 return _decode_object() if /\G\{/gc;
199              
200             # Array
201 96 100       245 return _decode_array() if /\G\[/gc;
202              
203             # Number
204 43 100       214 return 0 + $1 if /\G([-]?(?:0|[1-9][0-9]*)(?:\.[0-9]*)?(?:[eE][+-]?[0-9]+)?)/gc;
205              
206             # True
207 16 100       45 return true() if /\Gtrue/gc;
208              
209             # False
210 11 100       51 return false() if /\Gfalse/gc;
211              
212             # Null
213 7 100       29 return undef if /\Gnull/gc;
214              
215             # Invalid character
216 2         8 _throw('Expected string, array, object, number, boolean or null');
217             }
218              
219             sub _encode_array {
220 52     52   71 '[' . join(',', map { _encode_value($_) } @{$_[0]}) . ']';
  74         174  
  52         167  
221             }
222              
223             sub _encode_object {
224 19     19   31 my $object = shift;
225 19         66 my @pairs = map { _encode_string($_) . ':' . _encode_value($object->{$_}) } sort keys %$object;
  17         40  
226 19         115 return '{' . join(',', @pairs) . '}';
227             }
228              
229             sub _encode_string {
230 57     57   239 my $str = shift;
231 57         295 $str =~ s!([\x00-\x1f\\"/])!$REVERSE{$1}!gs;
232 57         444 return "\"$str\"";
233             }
234              
235             sub _encode_value {
236 155     155   241 my $value = shift;
237              
238             # Reference
239 155 100       415 if (my $ref = ref $value) {
240              
241             # Object
242 88 100       237 return _encode_object($value) if $ref eq 'HASH';
243              
244             # Array
245 69 100       171 return _encode_array($value) if $ref eq 'ARRAY';
246              
247             # True or false
248 17 100       45 return $$value ? 'true' : 'false' if $ref eq 'SCALAR';
    100          
249 15 100       53 return $value ? 'true' : 'false' if $ref eq 'JSON::PP::Boolean';
    100          
250              
251             # Everything else
252 6 100       18 return 'null' unless blessed $value;
253 5 100       61 return overload::Method($value, '""') ? _encode_string($value) : 'null' unless my $sub = $value->can('TO_JSON');
    100          
254 2         8 return _encode_value($value->$sub);
255             }
256              
257             # Null
258 67 100       153 return 'null' unless defined $value;
259              
260             # Boolean
261 63 100       156 return $value ? 'true' : 'false' if CORE_BOOLS && builtin::is_bool($value);
    100          
262              
263             # Number
264 75     75   766 no warnings 'numeric';
  75         162  
  75         37850  
265 61 100 100     624 return $value
      100        
      100        
266             if !utf8::is_utf8($value) && length((my $dummy = '') & $value) && 0 + $value eq $value && $value * 0 == 0;
267              
268             # String
269 38         85 return _encode_string($value);
270             }
271              
272             sub _throw {
273              
274             # Leading whitespace
275 18     18   45 /\G[\x20\x09\x0a\x0d]*/gc;
276              
277             # Context
278 18         37 my $context = 'Malformed JSON: ' . shift;
279 18 50       53 if (m/\G\z/gc) { $context .= ' before end of data' }
  0         0  
280             else {
281 18         110 my @lines = split /\n/, substr($_, 0, pos);
282 18   100     80 $context .= ' at line ' . @lines . ', offset ' . length(pop @lines || '');
283             }
284              
285 18         157 die "$context\n";
286             }
287              
288             1;
289              
290             =encoding utf8
291              
292             =head1 NAME
293              
294             Mojo::JSON - Minimalistic JSON
295              
296             =head1 SYNOPSIS
297              
298             use Mojo::JSON qw(decode_json encode_json);
299              
300             my $bytes = encode_json {foo => [1, 2], bar => 'hello!', baz => \1};
301             my $hash = decode_json $bytes;
302              
303             =head1 DESCRIPTION
304              
305             L is a minimalistic and possibly the fastest pure-Perl implementation of L
306             8259|https://tools.ietf.org/html/rfc8259>.
307              
308             It supports normal Perl data types like scalar, array reference, hash reference and will try to call the C
309             method on blessed references, or stringify them if it doesn't exist. Differentiating between strings and numbers in
310             Perl is hard, depending on how it has been used, a scalar can be both at the same time. The string value has a higher
311             precedence unless both representations are equivalent.
312              
313             [1, -2, 3] -> [1, -2, 3]
314             {"foo": "bar"} -> {foo => 'bar'}
315              
316             Literal names will be translated to and from L constants or a similar native Perl value.
317              
318             true -> Mojo::JSON->true
319             false -> Mojo::JSON->false
320             null -> undef
321              
322             In addition scalar references will be used to generate booleans, based on if their values are true or false.
323              
324             \1 -> true
325             \0 -> false
326              
327             The character C will always be escaped to prevent XSS attacks.
328              
329             "" -> "<\/script>"
330              
331             For better performance the optional module L (4.20+) will be used automatically if possible. This can
332             also be disabled with the C environment variable.
333              
334             =head1 FUNCTIONS
335              
336             L implements the following functions, which can be imported individually.
337              
338             =head2 decode_json
339              
340             my $value = decode_json $bytes;
341              
342             Decode JSON to Perl value and die if decoding fails.
343              
344             =head2 encode_json
345              
346             my $bytes = encode_json {i => '♥ mojolicious'};
347              
348             Encode Perl value to JSON.
349              
350             =head2 false
351              
352             my $false = false;
353              
354             False value, used because Perl has no native equivalent.
355              
356             =head2 from_json
357              
358             my $value = from_json $chars;
359              
360             Decode JSON text that is not C encoded to Perl value and die if decoding fails.
361              
362             =head2 j
363              
364             my $bytes = j [1, 2, 3];
365             my $bytes = j {i => '♥ mojolicious'};
366             my $value = j $bytes;
367              
368             Encode Perl data structure (which may only be an array reference or hash reference) or decode JSON, an C return
369             value indicates a bare C or that decoding failed.
370              
371             =head2 to_json
372              
373             my $chars = to_json {i => '♥ mojolicious'};
374              
375             Encode Perl value to JSON text without C encoding it.
376              
377             =head2 true
378              
379             my $true = true;
380              
381             True value, used because Perl has no native equivalent.
382              
383             =head1 SEE ALSO
384              
385             L, L, L.
386              
387             =cut