File Coverage

blib/lib/JSON/String.pm
Criterion Covered Total %
statement 62 63 98.4
branch 19 22 86.3
condition 4 6 66.6
subroutine 12 12 100.0
pod 1 2 50.0
total 98 105 93.3


line stmt bran cond sub pod time code
1 5     5   108746 use strict;
  5         8  
  5         149  
2 5     5   19 use warnings;
  5         4  
  5         168  
3              
4             package JSON::String;
5              
6 5     5   19 use Carp qw(croak);
  5         10  
  5         330  
7             our @CARP_NOT = qw(JSON::String::BaseHandler JSON::String::HASH JSON::String::ARRAY);
8 5     5   3197 use JSON;
  5         50422  
  5         19  
9              
10 5     5   2225 use JSON::String::ARRAY;
  5         13  
  5         139  
11 5     5   2172 use JSON::String::HASH;
  5         8  
  5         2270  
12              
13             our $VERSION = '0.1.0_03'; # VERSION
14              
15             sub tie {
16 25     25 0 25321 my($class, $string) = @_;
17 25         42 my $ref = \$_[1];
18              
19 25         55 my $data = _validate_string_ref($ref);
20 21         43 return _construct_object($data, $ref);
21             }
22              
23             sub _construct_object {
24 175     175   164 my($data, $str_ref, $encoder) = @_;
25              
26 175 50 66     341 croak('Either string ref or encoder sub expected, not both') if ($str_ref and $encoder);
27              
28 175 100       418 return $data unless ref $data;
29              
30 54 100       94 $encoder = _create_encoder($data, $str_ref) unless $encoder;
31              
32 54         49 my $self;
33 54 100       126 if (ref($data) eq 'ARRAY') {
    50          
34 22         32 foreach my $elt ( @$data ) {
35 53         72 $elt = _construct_object($elt, undef, $encoder);
36             }
37 22         40 $self = [];
38 22         87 CORE::tie @$self, 'JSON::String::ARRAY', data => $data, encoder => $encoder;
39             } elsif (ref($data) eq 'HASH') {
40 32         71 foreach my $key ( keys %$data ) {
41 50         84 $data->{$key} = _construct_object($data->{$key}, undef, $encoder);
42             }
43 32         40 $self = {};
44 32         136 CORE::tie %$self, 'JSON::String::HASH', data => $data, encoder => $encoder;
45             }
46              
47 54         117 return $self;
48             }
49              
50             {
51             my $codec = JSON->new->canonical;
52             sub codec {
53 53     53 1 6086 shift;
54 53 100       148 if (@_) {
55 1         3 $codec = shift;
56             }
57 53         233 return $codec;
58             }
59             }
60              
61             sub _create_encoder {
62 21     21   23 my($data, $str_ref) = @_;
63              
64 21         30 my $codec = codec;
65             return sub {
66 59     59   44 my $val;
67 59         43 my $error = do {
68 59         54 local $@;
69 59         84 $val = eval { $$str_ref = $codec->encode($data) };
  59         279  
70 59         896 $@;
71             };
72 59 100       160 croak("Error encoding data structure: $error") if $error;
73 58         88 return $val;
74 21         82 };
75             }
76              
77             sub _validate_string_ref {
78 25     25   25 my $ref = shift;
79              
80 25 100       86 unless (ref $ref eq 'SCALAR') {
81 1         9 croak q(Expected plain string, but got reference);
82             }
83 24 100       52 unless (defined $$ref) {
84 1         19 croak('Expected string, but got ');
85             }
86 23 100       66 unless (length $$ref) {
87 1         10 croak('Expected non-empty string');
88             }
89              
90 22         42 my $data = codec()->decode($$ref);
91              
92 21 50 66     356 unless (ref($data) eq 'ARRAY' or ref($data) eq 'HASH') {
93 0         0 croak('Cannot handle '.ref($data).' reference');
94             }
95 21         28 return $data;
96             }
97              
98             1;
99              
100             =pod
101              
102             =head1 NAME
103              
104             JSON::String - Automatically change a JSON string when a data structure changes
105              
106             =head1 SYNOPSIS
107              
108             my $json_string = q({ a: 1, b: 2, c: [ 4, 5, 6 ] });
109             my $data = JSON::String->tie($json_string);
110              
111             @{$data->{c}} = qw(this data changed);
112             # $json_string now contains '{ a: 1, b: 2, c: ["this", "data", "changed"] }'
113              
114             =head1 DESCRIPTION
115              
116             This module constructs a data structure that, when changed, automatically
117             changes the original string's contents to match the new data. Hashrefs and
118             arrayrefs are supported, and their values can be scalars, hashrefs or
119             arrayrefs.
120              
121             The JSON format does not handle recursive data, and an exception will be
122             thrown if the data structure is changed such that it has a loop.
123              
124             =head1 CONSTRUCTOR
125              
126             my $data = JSON::String->tie($json_string);
127              
128             Returns either a hashref or arrayref, depending on the input JSON string.
129             The string passed in must by valid JSON encoding either an arrayref or
130             hashref, otherwise it will throw an exception.
131              
132             The returned data structure is tied to the string such that when the data
133             changes, the JSON string stored in the variable will be changed to reflect
134             the new data. If the string changes, the data structure will _not_ change.
135              
136             =head2 Methods
137              
138             =over 4
139              
140             =item JSON::String->codec(); # returns a JSON instance
141              
142             =item JSON::String->codec($obj);
143              
144             Get or change the JSON codec object. The initial codec is created with
145             JSON->new->canonical()
146              
147             Any object can be used as the codec as long as it has C and
148             C methods. A data structure's codec does not change after it
149             is created. If the class's codec changes after creation, the data structure
150             will continue to use whatever codec was active when it was created.
151              
152             =back
153              
154             =head2 Mechanism
155              
156             This module uses Perl's C mechanism to perform its magic. The hash-
157             and arrayrefs that make up the returned data structure are references to tied
158             hashes and arrays. When their data changes, the top-level data structure is
159             re-encoded and stored back in the original variable.
160              
161             =head2 Diagnostics
162              
163             Error conditions are signalled with exceptions.
164              
165             =over 4
166              
167             =item Error encoding data structure: %s
168              
169             The codec's encode() method threw an exception when encoding the data structure.
170              
171             =item Cannot handle %s reference
172              
173             JSON::String->tie() was passed a string that did not decode to either a
174             hashref or arrayref.
175              
176             =item Expected plain string, but got reference
177              
178             JSON::String->tie() was passed a reference to something instead of a string.
179              
180             =item Expected string, but got
181              
182             JSON::String->tie() was passed undef instead of a string.
183              
184             =item Expected non-empty string
185              
186             JSON::String->tie() was passed an empty string.
187              
188             =back
189              
190             =head1 SEE ALSO
191              
192             L, L, L
193              
194             =head1 AUTHOR
195              
196             Anthony Brummett
197              
198             =head1 COPYRIGHT
199              
200             Copyright 2015, Anthony Brummett. This module is free software. It may
201             be used, redistributed and/or modified under the same terms as Perl itself.
202              
203             =cut