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             =item json_type_weaken
178              
179             This function can be used as an argument for L,
180             L or L functions to create weak
181             references suitable for complicated recursive structures. It depends
182             on L module.
183             See following example:
184              
185             my $struct = {
186             type => JSON_TYPE_STRING,
187             array => json_type_arrayof(JSON_TYPE_INT),
188             };
189             $struct->{recursive} = json_type_anyof(
190             json_type_weaken($struct),
191             json_type_arrayof(JSON_TYPE_STRING),
192             );
193              
194             =back
195              
196             =head1 AUTHOR
197              
198             Pali Epali@cpan.orgE
199              
200             =head1 COPYRIGHT & LICENSE
201              
202             Copyright (c) 2017, GoodData Corporation. All rights reserved.
203              
204             This module is available under the same licences as perl, the Artistic
205             license and the GPL.
206              
207             =cut
208              
209 2     2   923 use strict;
  2         4  
  2         49  
210 2     2   8 use warnings;
  2         5  
  2         164  
211              
212             BEGIN {
213 2 50   2   5 if (eval { require Scalar::Util }) {
  2         14  
214 2         193 Scalar::Util->import('weaken');
215             } else {
216 0         0 *weaken = sub($) { die 'Scalar::Util is required for weaken' };
  0         0  
217             }
218             }
219              
220             # This exports needed XS constants to perl
221 2     2   12 use Cpanel::JSON::XS ();
  2         2  
  2         35  
222              
223 2     2   8 use Exporter;
  2         3635  
  2         232  
224             our @ISA = qw(Exporter);
225             our @EXPORT = our @EXPORT_OK = qw(
226             json_type_arrayof
227             json_type_hashof
228             json_type_anyof
229             json_type_null_or_anyof
230             json_type_weaken
231             JSON_TYPE_NULL
232             JSON_TYPE_BOOL
233             JSON_TYPE_INT
234             JSON_TYPE_FLOAT
235             JSON_TYPE_STRING
236             JSON_TYPE_BOOL_OR_NULL
237             JSON_TYPE_INT_OR_NULL
238             JSON_TYPE_FLOAT_OR_NULL
239             JSON_TYPE_STRING_OR_NULL
240             JSON_TYPE_ARRAYOF_CLASS
241             JSON_TYPE_HASHOF_CLASS
242             JSON_TYPE_ANYOF_CLASS
243             );
244              
245 2     2   14 use constant JSON_TYPE_WEAKEN_CLASS => 'Cpanel::JSON::XS::Type::Weaken';
  2         90  
  2         1099  
246              
247             sub json_type_anyof {
248 86     86 1 47130 my ($scalar, $array, $hash);
249 86         0 my ($scalar_weaken, $array_weaken, $hash_weaken);
250 86         146 foreach (@_) {
251 156         178 my $type = $_;
252 156         226 my $ref = ref($_);
253 156         163 my $weaken;
254 156 100       266 if ($ref eq JSON_TYPE_WEAKEN_CLASS) {
255 1         1 $type = ${$type};
  1         3  
256 1         2 $ref = ref($type);
257 1         1 $weaken = 1;
258             }
259 156 100 100     422 if ($ref eq '') {
    100 100        
    100          
260 77 100       153 die 'Only one scalar type can be specified in anyof' if defined $scalar;
261 76         85 $scalar = $type;
262 76         107 $scalar_weaken = $weaken;
263             } elsif ($ref eq 'ARRAY' or $ref eq JSON_TYPE_ARRAYOF_CLASS) {
264 41 100       89 die 'Only one array type can be specified in anyof' if defined $array;
265 38         43 $array = $type;
266 38         56 $array_weaken = $weaken;
267             } elsif ($ref eq 'HASH' or $ref eq JSON_TYPE_HASHOF_CLASS) {
268 37 100       85 die 'Only one hash type can be specified in anyof' if defined $hash;
269 34         42 $hash = $type;
270 34         53 $hash_weaken = $weaken;
271             } else {
272 1         10 die 'Only scalar, array or hash can be specified in anyof';
273             }
274             }
275 78         146 my $type = [$scalar, $array, $hash];
276 78 50       121 weaken $type->[0] if $scalar_weaken;
277 78 50       114 weaken $type->[1] if $array_weaken;
278 78 100       110 weaken $type->[2] if $hash_weaken;
279 78         551 return bless $type, JSON_TYPE_ANYOF_CLASS;
280             }
281              
282             sub json_type_null_or_anyof {
283 4     4 1 11 foreach (@_) {
284 4 100       20 die 'Scalar cannot be specified in null_or_anyof' if ref($_) eq '';
285             }
286 3         9 return json_type_anyof(JSON_TYPE_CAN_BE_NULL, @_);
287             }
288              
289             sub json_type_arrayof {
290 16 100   16 1 142142 die 'Exactly one type must be specified in arrayof' if scalar @_ != 1;
291 15         22 my $type = $_[0];
292 15 100       33 if (ref($type) eq JSON_TYPE_WEAKEN_CLASS) {
293 1         1 $type = ${$type};
  1         3  
294 1         4 weaken $type;
295             }
296 15         60 return bless \$type, JSON_TYPE_ARRAYOF_CLASS;
297             }
298              
299             sub json_type_hashof {
300 9 100   9 1 1597 die 'Exactly one type must be specified in hashof' if scalar @_ != 1;
301 8         12 my $type = $_[0];
302 8 100       17 if (ref($type) eq JSON_TYPE_WEAKEN_CLASS) {
303 1         3 $type = ${$type};
  1         2  
304 1         4 weaken $type;
305             }
306 8         28 return bless \$type, JSON_TYPE_HASHOF_CLASS;
307             }
308              
309             sub json_type_weaken {
310 3 50   3 1 6719 die 'Exactly one type must be specified in weaken' if scalar @_ != 1;
311 3 50       11 die 'Scalar cannot be specfied in weaken' if ref($_[0]) eq '';
312 3         21 return bless \(my $type = $_[0]), JSON_TYPE_WEAKEN_CLASS;
313             }
314              
315             1;