File Coverage

blib/lib/Dancer2/Serializer/JSON.pm
Criterion Covered Total %
statement 51 61 83.6
branch 17 24 70.8
condition n/a
subroutine 13 14 92.8
pod 2 4 50.0
total 83 103 80.5


line stmt bran cond sub pod time code
1             package Dancer2::Serializer::JSON;
2             # ABSTRACT: Serializer for handling JSON data
3             $Dancer2::Serializer::JSON::VERSION = '2.1.0';
4 25     25   443315 use Moo;
  25         19365  
  25         237  
5 25     25   20893 use Ref::Util qw< is_arrayref is_hashref >;
  25         5478  
  25         2024  
6 25     25   588 use JSON::MaybeXS ();
  25         8258  
  25         835  
7 25     25   839 use Encode qw(decode FB_CROAK);
  25         22830  
  25         1850  
8 25     25   161 use Scalar::Util 'blessed';
  25         70  
  25         28680  
9              
10             with 'Dancer2::Core::Role::Serializer';
11              
12             has '+content_type' => ( default => sub {'application/json'} );
13              
14             # helpers
15 6     6 1 178 sub from_json { __PACKAGE__->deserialize(@_) }
16              
17 24     24 1 3430 sub to_json { __PACKAGE__->serialize(@_) }
18              
19             sub decode_json {
20 2     2 0 4041 my ( $entity ) = @_;
21              
22 2         90 JSON::MaybeXS::decode_json($entity);
23             }
24              
25             sub encode_json {
26 2     2 0 5 my ( $entity ) = @_;
27              
28 2         7 JSON::MaybeXS::encode_json(_ensure_characters($entity));
29             }
30              
31             # class definition
32             sub serialize {
33             my ( $self, $entity, $options ) = @_;
34              
35             my $config = blessed $self ? $self->config : {};
36             my $strict_utf8 = $config->{strict_utf8};
37             $options ||= {};
38              
39             foreach (keys %$config) {
40             $options->{$_} = $config->{$_} unless exists $options->{$_};
41             }
42              
43             $options->{utf8} = 1;
44             exists $options->{strict_utf8}
45             and $strict_utf8 = delete $options->{strict_utf8};
46             $entity = _ensure_characters( $entity, $strict_utf8, $self );
47             JSON::MaybeXS->new($options)->encode($entity);
48             }
49              
50             sub deserialize {
51             my ( $self, $entity, $options ) = @_;
52              
53             $options ||= {};
54             $options->{utf8} = 1;
55             delete $options->{strict_utf8};
56             JSON::MaybeXS->new($options)->decode($entity);
57             }
58              
59             my $HAS_UNICODE_UTF8 = eval { require Unicode::UTF8; 1; };
60              
61             sub _valid_utf8 {
62 2     2   4 my ($bytes) = @_;
63 2 50       15 return Unicode::UTF8::valid_utf8($bytes) if $HAS_UNICODE_UTF8;
64 0         0 return eval { decode( 'UTF-8', $bytes, FB_CROAK ); 1 };
  0         0  
  0         0  
65             }
66              
67             sub _decode_utf8 {
68 0     0   0 my ($bytes) = @_;
69 0 0       0 return Unicode::UTF8::decode_utf8($bytes) if $HAS_UNICODE_UTF8;
70 0         0 return decode( 'UTF-8', $bytes );
71             }
72              
73             sub _ensure_characters {
74 200     200   535 my ( $entity, $strict_utf8, $self ) = @_;
75              
76 200 100       475 return $entity if !defined $entity;
77 199 100       557 return _ensure_scalar( $entity, $strict_utf8, $self ) if !ref $entity;
78              
79 78 100       262 if ( is_arrayref($entity) ) {
80 15         22 for my $i ( 0 .. $#{$entity} ) {
  15         58  
81 38         125 $entity->[$i] = _ensure_characters( $entity->[$i], $strict_utf8, $self );
82             }
83 15         47 return $entity;
84             }
85              
86 63 50       206 if ( is_hashref($entity) ) {
87 63         106 for my $key ( keys %{$entity} ) {
  63         229  
88 93         199 my $value = $entity->{$key};
89 93         233 my $decoded_key = _ensure_scalar( $key, $strict_utf8, $self );
90 93         365 my $decoded_value =
91             _ensure_characters( $value, $strict_utf8, $self );
92              
93 92 50       246 if ( $decoded_key ne $key ) {
94 0         0 delete $entity->{$key};
95 0         0 $entity->{$decoded_key} = $decoded_value;
96             } else {
97 92         286 $entity->{$key} = $decoded_value;
98             }
99             }
100 62         297 return $entity;
101             }
102              
103 0         0 return $entity;
104             }
105              
106             sub _ensure_scalar {
107 214     214   442 my ( $value, $strict_utf8, $self ) = @_;
108              
109 214 100       604 return $value if utf8::is_utf8($value);
110 206 100       823 return $value if $value !~ /[\x80-\xFF]/;
111 2 50       9 return _decode_utf8($value) if _valid_utf8($value);
112              
113 2         8 _invalid_utf8( $strict_utf8, $self );
114 1         5 return $value;
115             }
116              
117             sub _invalid_utf8 {
118 2     2   6 my ( $strict_utf8, $self ) = @_;
119 2         3 my $msg = 'Invalid UTF-8 in JSON data';
120              
121 2 100       34 $strict_utf8
122             and die "$msg\n";
123              
124 1 50       3 if ( blessed($self) ) {
125 1         11 $self->log_cb->( warning => "$msg; leaving bytes unchanged" );
126             } else {
127 0           warn "$msg; leaving bytes unchanged\n";
128             }
129             }
130              
131             1;
132              
133             __END__