File Coverage

blib/lib/Cpanel/JSON/XS/Type.pm
Criterion Covered Total %
statement 65 67 97.0
branch 31 36 86.1
condition 6 6 100.0
subroutine 11 11 100.0
pod 5 5 100.0
total 118 125 94.4


line stmt bran cond sub pod time code
1             package Cpanel::JSON::XS::Type;
2              
3             =pod
4              
5             =head1 NAME
6              
7             Cpanel::JSON::XS::Type - Type support for JSON encode
8              
9             =head1 SYNOPSIS
10              
11             use Cpanel::JSON::XS;
12             use Cpanel::JSON::XS::Type;
13              
14              
15             encode_json([10, "10", 10.25], [JSON_TYPE_INT, JSON_TYPE_INT, JSON_TYPE_STRING]);
16             # '[10,10,"10.25"]'
17              
18             encode_json([10, "10", 10.25], json_type_arrayof(JSON_TYPE_INT));
19             # '[10,10,10]'
20              
21             encode_json(1, JSON_TYPE_BOOL);
22             # 'true'
23              
24             my $perl_struct = { key1 => 1, key2 => "2", key3 => 1 };
25             my $type_spec = { key1 => JSON_TYPE_STRING, key2 => JSON_TYPE_INT, key3 => JSON_TYPE_BOOL };
26             my $json_string = encode_json($perl_struct, $type_spec);
27             # '{"key1":"1","key2":2,"key3":true}'
28              
29             my $perl_struct = { key1 => "value1", key2 => "value2", key3 => 0, key4 => 1, key5 => "string", key6 => "string2" };
30             my $type_spec = json_type_hashof(JSON_TYPE_STRING);
31             my $json_string = encode_json($perl_struct, $type_spec);
32             # '{"key1":"value1","key2":"value2","key3":"0","key4":"1","key5":"string","key6":"string2"}'
33              
34             my $perl_struct = { key1 => { key2 => [ 10, "10", 10.6 ] }, key3 => "10.5" };
35             my $type_spec = { key1 => json_type_anyof(JSON_TYPE_FLOAT, json_type_hashof(json_type_arrayof(JSON_TYPE_INT))), key3 => JSON_TYPE_FLOAT };
36             my $json_string = encode_json($perl_struct, $type_spec);
37             # '{"key1":{"key2":[10,10,10]},"key3":10.5}'
38              
39              
40             my $value = decode_json('false', 1, my $type);
41             # $value is 0 and $type is JSON_TYPE_BOOL
42              
43             my $value = decode_json('0', 1, my $type);
44             # $value is 0 and $type is JSON_TYPE_INT
45              
46             my $value = decode_json('"0"', 1, my $type);
47             # $value is 0 and $type is JSON_TYPE_STRING
48              
49             my $json_string = '{"key1":{"key2":[10,"10",10.6]},"key3":"10.5"}';
50             my $perl_struct = decode_json($json_string, 0, my $type_spec);
51             # $perl_struct is { key1 => { key2 => [ 10, 10, 10.6 ] }, key3 => 10.5 }
52             # $type_spec is { key1 => { key2 => [ JSON_TYPE_INT, JSON_TYPE_STRING, JSON_TYPE_FLOAT ] }, key3 => JSON_TYPE_STRING }
53              
54             =head1 DESCRIPTION
55              
56             This module provides stable JSON type support for the
57             L encoder which doesn't depend on
58             any internal perl scalar flags or characteristics. Also it provides
59             real JSON types for L decoder.
60              
61             In most cases perl structures passed to
62             L come from other functions
63             or from other modules and caller of Cpanel::JSON::XS module does not
64             have control of internals or they are subject of change. So it is not
65             easy to support enforcing types as described in the
66             L section.
67              
68             For services based on JSON contents it is sometimes needed to correctly
69             process and enforce JSON types.
70              
71             The function L takes optional
72             third scalar parameter and fills it with specification of json types.
73              
74             The function L takes a perl
75             structure as its input and optionally also a json type specification in
76             the second parameter.
77              
78             If the specification is not provided (or is undef) internal perl
79             scalar flags are used for the resulting JSON type. The internal flags
80             can be changed by perl itself, but also by external modules. Which
81             means that types in resulting JSON string aren't stable. Specially it
82             does not work reliable for dual vars and scalars which were used in
83             both numeric and string operations. See L
84             scalars|Cpanel::JSON::XS/simple scalars>.
85              
86             To enforce that specification is always provided use C.
87             In this case when C is called without second argument (or is
88             undef) then it croaks. It applies recursively for all sub-structures.
89              
90             =head2 JSON type specification for scalars:
91              
92             =over 4
93              
94             =item JSON_TYPE_BOOL
95              
96             It enforces JSON boolean in resulting JSON, i.e. either C or
97             C. For determining whether the scalar passed to the encoder
98             is true, standard perl boolean logic is used.
99              
100             =item JSON_TYPE_INT
101              
102             It enforces JSON number without fraction part in the resulting JSON.
103             Equivalent of perl function L is used for conversion.
104              
105             =item JSON_TYPE_FLOAT
106              
107             It enforces JSON number with fraction part in the resulting JSON.
108             Equivalent of perl operation C<+0> is used for conversion.
109              
110             =item JSON_TYPE_STRING
111              
112             It enforces JSON string type in the resulting JSON.
113              
114             =item JSON_TYPE_NULL
115              
116             It represents JSON C value. Makes sense only when passing
117             perl's C value.
118              
119             =back
120              
121             For each type, there also exists a type with the suffix C<_OR_NULL>
122             which encodes perl's C into JSON C. Without type with
123             suffix C<_OR_NULL> perl's C is converted to specific type
124             according to above rules.
125              
126             =head2 JSON type specification for arrays:
127              
128             =over 4
129              
130             =item [...]
131              
132             The array must contain the same number of elements as in the perl
133             array passed for encoding. Each element of the array describes the
134             JSON type which is enforced for the corresponding element of the
135             perl array.
136              
137             =item json_type_arrayof
138              
139             This function takes a JSON type specification as its argument which
140             is enforced for every element of the passed perl array.
141              
142             =back
143              
144             =head2 JSON type specification for hashes:
145              
146             =over 4
147              
148             =item {...}
149              
150             Each hash value for corresponding key describes the JSON type
151             specification for values of passed perl hash structure. Keys in hash
152             which are not present in passed perl hash structure are simple
153             ignored and not used.
154              
155             =item json_type_hashof
156              
157             This function takes a JSON type specification as its argument which
158             is enforced for every value of passed perl hash structure.
159              
160             =back
161              
162             =head2 JSON type specification for alternatives:
163              
164             =over 4
165              
166             =item json_type_anyof
167              
168             This function takes a list of JSON type alternative specifications
169             (maximally one scalar, one array, and one hash) as its input and the
170             JSON encoder chooses one that matches.
171              
172             =item json_type_null_or_anyof
173              
174             Like L|/json_type_anyof>, but scalar can be only
175             perl's C.
176              
177             =back
178              
179             =head2 Recursive specifications
180              
181             =over 4
182              
183             =item json_type_weaken
184              
185             This function can be used as an argument for L,
186             L or L functions to create weak
187             references suitable for complicated recursive structures. It depends
188             on L module.
189             See following example:
190              
191             my $struct = {
192             type => JSON_TYPE_STRING,
193             array => json_type_arrayof(JSON_TYPE_INT),
194             };
195             $struct->{recursive} = json_type_anyof(
196             json_type_weaken($struct),
197             json_type_arrayof(JSON_TYPE_STRING),
198             );
199              
200             If you want to encode all perl scalars to JSON string types despite
201             how complicated is input perl structure you can define JSON type
202             specification for alternatives recursively. It could be defined as:
203              
204             my $type = json_type_anyof();
205             $type->[0] = JSON_TYPE_STRING_OR_NULL;
206             $type->[1] = json_type_arrayof(json_type_weaken($type));
207             $type->[2] = json_type_hashof(json_type_weaken($type));
208              
209             print encode_json([ 10, "10", { key => 10 } ], $type);
210             # ["10","10",{"key":"10"}]
211              
212             An alternative solution for encoding all scalars to JSON strings is to
213             use C method of L itself:
214              
215             my $json = Cpanel::JSON::XS->new->type_all_string;
216             print $json->encode([ 10, "10", { key => 10 } ]);
217             # ["10","10",{"key":"10"}]
218              
219             =back
220              
221             =head1 AUTHOR
222              
223             Pali Epali@cpan.orgE
224              
225             =head1 COPYRIGHT & LICENSE
226              
227             Copyright (c) 2017, GoodData Corporation. All rights reserved.
228              
229             This module is available under the same licences as perl, the Artistic
230             license and the GPL.
231              
232             =cut
233              
234 2     2   893 use strict;
  2         4  
  2         47  
235 2     2   9 use warnings;
  2         4  
  2         157  
236              
237             BEGIN {
238 2 50   2   6 if (eval { require Scalar::Util }) {
  2         12  
239 2         197 Scalar::Util->import('weaken');
240             } else {
241 0         0 *weaken = sub($) { die 'Scalar::Util is required for weaken' };
  0         0  
242             }
243             }
244              
245             # This exports needed XS constants to perl
246 2     2   12 use Cpanel::JSON::XS ();
  2         2  
  2         44  
247              
248 2     2   13 use Exporter;
  2         3  
  2         200  
249             our @ISA = qw(Exporter);
250             our @EXPORT = our @EXPORT_OK = qw(
251             json_type_arrayof
252             json_type_hashof
253             json_type_anyof
254             json_type_null_or_anyof
255             json_type_weaken
256             JSON_TYPE_NULL
257             JSON_TYPE_BOOL
258             JSON_TYPE_INT
259             JSON_TYPE_FLOAT
260             JSON_TYPE_STRING
261             JSON_TYPE_BOOL_OR_NULL
262             JSON_TYPE_INT_OR_NULL
263             JSON_TYPE_FLOAT_OR_NULL
264             JSON_TYPE_STRING_OR_NULL
265             JSON_TYPE_ARRAYOF_CLASS
266             JSON_TYPE_HASHOF_CLASS
267             JSON_TYPE_ANYOF_CLASS
268             );
269              
270 2     2   10 use constant JSON_TYPE_WEAKEN_CLASS => 'Cpanel::JSON::XS::Type::Weaken';
  2         4  
  2         1016  
271              
272             sub json_type_anyof {
273 86     86 1 39492 my ($scalar, $array, $hash);
274 86         0 my ($scalar_weaken, $array_weaken, $hash_weaken);
275 86         139 foreach (@_) {
276 156         183 my $type = $_;
277 156         203 my $ref = ref($_);
278 156         166 my $weaken;
279 156 100       251 if ($ref eq JSON_TYPE_WEAKEN_CLASS) {
280 1         2 $type = ${$type};
  1         3  
281 1         2 $ref = ref($type);
282 1         2 $weaken = 1;
283             }
284 156 100 100     415 if ($ref eq '') {
    100 100        
    100          
285 77 100       124 die 'Only one scalar type can be specified in anyof' if defined $scalar;
286 76         80 $scalar = $type;
287 76         100 $scalar_weaken = $weaken;
288             } elsif ($ref eq 'ARRAY' or $ref eq JSON_TYPE_ARRAYOF_CLASS) {
289 41 100       81 die 'Only one array type can be specified in anyof' if defined $array;
290 38         43 $array = $type;
291 38         70 $array_weaken = $weaken;
292             } elsif ($ref eq 'HASH' or $ref eq JSON_TYPE_HASHOF_CLASS) {
293 37 100       84 die 'Only one hash type can be specified in anyof' if defined $hash;
294 34         39 $hash = $type;
295 34         53 $hash_weaken = $weaken;
296             } else {
297 1         9 die 'Only scalar, array or hash can be specified in anyof';
298             }
299             }
300 78         128 my $type = [$scalar, $array, $hash];
301 78 50       126 weaken $type->[0] if $scalar_weaken;
302 78 50       116 weaken $type->[1] if $array_weaken;
303 78 100       114 weaken $type->[2] if $hash_weaken;
304 78         519 return bless $type, JSON_TYPE_ANYOF_CLASS;
305             }
306              
307             sub json_type_null_or_anyof {
308 4     4 1 10 foreach (@_) {
309 4 100       30 die 'Scalar cannot be specified in null_or_anyof' if ref($_) eq '';
310             }
311 3         7 return json_type_anyof(JSON_TYPE_CAN_BE_NULL, @_);
312             }
313              
314             sub json_type_arrayof {
315 16 100   16 1 109488 die 'Exactly one type must be specified in arrayof' if scalar @_ != 1;
316 15         24 my $type = $_[0];
317 15 100       36 if (ref($type) eq JSON_TYPE_WEAKEN_CLASS) {
318 1         2 $type = ${$type};
  1         2  
319 1         3 weaken $type;
320             }
321 15         89 return bless \$type, JSON_TYPE_ARRAYOF_CLASS;
322             }
323              
324             sub json_type_hashof {
325 9 100   9 1 1305 die 'Exactly one type must be specified in hashof' if scalar @_ != 1;
326 8         11 my $type = $_[0];
327 8 100       14 if (ref($type) eq JSON_TYPE_WEAKEN_CLASS) {
328 1         3 $type = ${$type};
  1         3  
329 1         3 weaken $type;
330             }
331 8         29 return bless \$type, JSON_TYPE_HASHOF_CLASS;
332             }
333              
334             sub json_type_weaken {
335 3 50   3 1 5214 die 'Exactly one type must be specified in weaken' if scalar @_ != 1;
336 3 50       9 die 'Scalar cannot be specfied in weaken' if ref($_[0]) eq '';
337 3         18 return bless \(my $type = $_[0]), JSON_TYPE_WEAKEN_CLASS;
338             }
339              
340             1;