File Coverage

blib/lib/RapidApp/JSON/MixedEncoder.pm
Criterion Covered Total %
statement 26 37 70.2
branch 7 14 50.0
condition 5 21 23.8
subroutine 9 13 69.2
pod 3 8 37.5
total 50 93 53.7


line stmt bran cond sub pod time code
1             package RapidApp::JSON::MixedEncoder;
2              
3 6     6   37 use strict;
  6         10  
  6         166  
4 6     6   27 use warnings;
  6         15  
  6         141  
5 6     6   29 use Scalar::Util 'blessed';
  6         11  
  6         255  
6 6     6   39 use Data::Dumper;
  6         15  
  6         258  
7 6     6   33 use base 'JSON::PP';
  6         15  
  6         3816  
8              
9             our @EXPORT = qw{
10             encode_json decode_json encode_json_utf8 decode_json_utf8 encode_json_ascii decode_json_ascii
11             };
12              
13             # ---
14             # These are values that we might encounter as ScalarRefs and how to
15             # translate them into safe values for the JSON encoder. There are
16             # only a few cases that I am aware of so far, but as new values are
17             # identified this is where they should be put.
18             # (Note: \0 and \1 are already handled and expected by the JSON encoder)
19             # (Note: vals are lc before being tested, so \'NULL' is already seen as \'null')
20             our %SCALARREF_VALUE_MAP = (
21            
22             # This has been seen in 'default_value' in sources generated by
23             # Schema::Loader from SQLite databases
24             "null" => undef,
25            
26             # This has also been seen in 'default_value' generated by S::L. Also
27             # setting this to undef because I'm not aware of any other better value
28             "current_timestamp" => undef,
29            
30             # Another value reported seen for 'default_value' generated by S::L
31             'now()' => undef,
32            
33             # these have been seen in 'default_value' in sources generated by
34             # Schema::Loader from PostgreSQL databases ("boolean" column type)
35             "true" => \1,
36             "false" => \0,
37            
38             # Add additional cases here ...
39             );
40             # ---
41              
42              
43             # copied from JSON::PP
44             my $JSON; # cache
45             sub encode_json ($) { # encode
46 1   33 1 1 14 ($JSON ||= __PACKAGE__->new)->encode($_[0]);
47             }
48             sub decode_json ($) { # decode
49 0   0 0 1 0 ($JSON ||= __PACKAGE__->new)->decode($_[0]);
50             }
51              
52             my $JSONUtf8; # cache
53             sub encode_json_utf8 ($) { # encode
54 5   66 5 0 57 ($JSONUtf8 ||= __PACKAGE__->new->utf8)->encode($_[0]);
55             }
56             sub decode_json_utf8 ($) { # decode
57 0   0 0 0 0 ($JSONUtf8 ||= __PACKAGE__->new->utf8)->decode($_[0]);
58             }
59              
60             ## ---
61             ## Note: even though it is "ascii" encoding of unicode characters will still work
62             ## and encode high characters as \uXXXX (e.g. \x{1f4a9}) and this is often the best
63             ## route to support unicode when working with JSON
64             my $JSONascii; # cache
65             sub encode_json_ascii ($) { # encode
66 0   0 0 0 0 ($JSONascii ||= __PACKAGE__->new->ascii)->encode($_[0]);
67             }
68             sub decode_json_ascii ($) { # decode
69 0   0 0 0 0 ($JSONascii ||= __PACKAGE__->new->ascii)->decode($_[0]);
70             }
71             # ---
72              
73              
74             sub new {
75 13     13 1 127 return bless JSON::PP->new->allow_blessed->convert_blessed->allow_nonref, __PACKAGE__;
76             }
77              
78              
79              
80             # We need to do this so that JSON won't quote the output of our
81             # TO_JSON method and will allow us to return invalid JSON...
82             # In this case, we're actually using the JSON lib to generate
83             # JavaScript (with functions), not JSON. We're also handling
84             # some special ScalarRef values to prevent JSON exceptions
85             sub object_to_json {
86 1715     1715 0 58427 my ($self, $obj)= @_;
87            
88 1715 50       3550 if (ref($obj) eq 'CODE') {
89 0         0 my $val = $obj->();
90 0 0       0 $val = '0' if ("$val" eq '0'); # <-- FIXME: there is a bug someplace
91 0         0 return $self->object_to_json( $val );
92             }
93            
94             # New: support on-the-fly calling of closures
95             #return $self->object_to_json( $obj->() ) if (ref($obj) eq 'CODE');
96            
97 1715 100       3356 if(ref($obj) eq 'SCALAR') {
    100          
98 274         375 my $val = $$obj;
99             # By design \0 and \1 are expected and will be handled as true/false. But,
100             # we don't expect to see any other ScalarRef values normally. But we'll
101             # handle them on a case-by-case basis below:
102 274 50 66     731 if ("$val" ne "0" and "$val" ne "1") {
103 0 0       0 if(exists $SCALARREF_VALUE_MAP{lc($val)}) {
104 0         0 $obj = $SCALARREF_VALUE_MAP{lc($val)};
105             }
106             else {
107             # This is a ScalarRef value that we don't know how to handle.
108             # Default it to undef but throw a warning
109 0         0 $obj = undef;
110 0         0 warn join("\n",
111             "\n RapidApp::JSON::MixedEncoder: encounterd unknown ScalarRef",
112             " value '$val' - will be encoded as 'null' in JSON data.",
113             " This is a BUG. Please report this message to RapidApp developers\n"
114             );
115             }
116             }
117             }
118             elsif (blessed($obj)) {
119             # This handles special objects which implement a TO_JSON_RAW method,
120             # like RapidApp::JSONFunc which will return a raw function (JavaScript,
121             # *not* nomral JSON)
122 43         128 my $method = $obj->can('TO_JSON_RAW');
123 43 50       135 return $method->($obj) if defined $method;
124             }
125            
126            
127 1672         3053 return $self->next::method($obj);
128             }
129              
130             1;