File Coverage

blib/lib/YAML/PP/Schema/JSON.pm
Criterion Covered Total %
statement 87 87 100.0
branch 18 20 90.0
condition 4 5 80.0
subroutine 15 15 100.0
pod 6 6 100.0
total 130 133 97.7


line stmt bran cond sub pod time code
1 45     45   145862 use strict;
  45         85  
  45         2907  
2 45     45   178 use warnings;
  45         73  
  45         3011  
3             package YAML::PP::Schema::JSON;
4              
5             our $VERSION = 'v0.39.0'; # VERSION
6              
7 45     45   195 use base 'Exporter';
  45         64  
  45         5827  
8             our @EXPORT_OK = qw/
9             represent_int represent_float represent_literal represent_bool
10             represent_undef
11             /;
12              
13 45     45   215 use B;
  45         65  
  45         1610  
14 45     45   180 use Carp qw/ croak /;
  45         67  
  45         2306  
15              
16 45     45   1266 use YAML::PP::Common qw/ YAML_PLAIN_SCALAR_STYLE YAML_SINGLE_QUOTED_SCALAR_STYLE /;
  45         88  
  45         44261  
17              
18             my $RE_INT = qr{^(-?(?:0|[1-9][0-9]*))$};
19             my $RE_FLOAT = qr{^(-?(?:0|[1-9][0-9]*)(?:\.[0-9]*)?(?:[eE][+-]?[0-9]+)?)$};
20              
21 388     388   1689 sub _to_int { 0 + $_[2]->[0] }
22              
23             # DaTa++ && shmem++
24 76     76   774 sub _to_float { unpack F => pack F => $_[2]->[0] }
25              
26             sub register {
27 22     22 1 63 my ($self, %args) = @_;
28 22         41 my $schema = $args{schema};
29 22         39 my $options = $args{options};
30 22         32 my $empty_null = 0;
31 22         43 for my $opt (@$options) {
32 2 50       7 if ($opt eq 'empty=str') {
    100          
33             }
34             elsif ($opt eq 'empty=null') {
35 1         2 $empty_null = 1;
36             }
37             else {
38 1         232 croak "Invalid option for JSON Schema: '$opt'";
39             }
40             }
41              
42             $schema->add_resolver(
43 21         90 tag => 'tag:yaml.org,2002:null',
44             match => [ equals => null => undef ],
45             );
46 21 100       49 if ($empty_null) {
47 1         3 $schema->add_resolver(
48             tag => 'tag:yaml.org,2002:null',
49             match => [ equals => '' => undef ],
50             implicit => 1,
51             );
52             }
53             else {
54 20         69 $schema->add_resolver(
55             tag => 'tag:yaml.org,2002:str',
56             match => [ equals => '' => '' ],
57             implicit => 1,
58             );
59             }
60 21         75 $schema->add_resolver(
61             tag => 'tag:yaml.org,2002:bool',
62             match => [ equals => true => $schema->true ],
63             );
64 21         65 $schema->add_resolver(
65             tag => 'tag:yaml.org,2002:bool',
66             match => [ equals => false => $schema->false ],
67             );
68 21         83 $schema->add_resolver(
69             tag => 'tag:yaml.org,2002:int',
70             match => [ regex => $RE_INT => \&_to_int ],
71             );
72 21         79 $schema->add_resolver(
73             tag => 'tag:yaml.org,2002:float',
74             match => [ regex => $RE_FLOAT => \&_to_float ],
75             );
76             $schema->add_resolver(
77             tag => 'tag:yaml.org,2002:str',
78 21     338   134 match => [ all => sub { $_[1]->{value} } ],
  338         1161  
79             );
80              
81 21         76 $schema->add_representer(
82             undefined => \&represent_undef,
83             );
84              
85 21         33 my $int_flags = B::SVp_IOK;
86 21         29 my $float_flags = B::SVp_NOK;
87 21         58 $schema->add_representer(
88             flags => $int_flags,
89             code => \&represent_int,
90             );
91 21         71 my %special = ( (0+'nan').'' => '.nan', (0+'inf').'' => '.inf', (0-'inf').'' => '-.inf' );
92 21         70 $schema->add_representer(
93             flags => $float_flags,
94             code => \&represent_float,
95             );
96             $schema->add_representer(
97             equals => $_,
98             code => \&represent_literal,
99 21         75 ) for ("", qw/ true false null /);
100 21         668 $schema->add_representer(
101             regex => qr{$RE_INT|$RE_FLOAT},
102             code => \&represent_literal,
103             );
104              
105 21 50       55 if ($schema->bool_class) {
106 21         45 for my $class (@{ $schema->bool_class }) {
  21         71  
107 21 100       45 if ($class eq 'perl') {
108 19         60 $schema->add_representer(
109             bool => 1,
110             code => \&represent_bool,
111             );
112 19         34 next;
113             }
114             $schema->add_representer(
115 2         4 class_equals => $class,
116             code => \&represent_bool,
117             );
118             }
119             }
120              
121 21         86 return;
122             }
123              
124             sub represent_undef {
125 110     110 1 179 my ($rep, $node) = @_;
126 110         242 $node->{style} = YAML_PLAIN_SCALAR_STYLE;
127 110         196 $node->{data} = 'null';
128 110         323 return 1;
129             }
130              
131             sub represent_literal {
132 219     219 1 381 my ($rep, $node) = @_;
133 219   100     877 $node->{style} ||= YAML_SINGLE_QUOTED_SCALAR_STYLE;
134 219         414 $node->{data} = "$node->{value}";
135 219         685 return 1;
136             }
137              
138              
139             sub represent_int {
140 386     386 1 1138 my ($rep, $node) = @_;
141 386 100       1162 if (int($node->{value}) ne $node->{value}) {
142 7         19 return 0;
143             }
144 379         627 $node->{style} = YAML_PLAIN_SCALAR_STYLE;
145 379         772 $node->{data} = "$node->{value}";
146 379         1115 return 1;
147             }
148              
149             my %special = (
150             (0+'nan').'' => '.nan',
151             (0+'inf').'' => '.inf',
152             (0-'inf').'' => '-.inf'
153             );
154             sub represent_float {
155 131     131 1 175 my ($rep, $node) = @_;
156 131 100       646 if (exists $special{ $node->{value} }) {
157 48         91 $node->{style} = YAML_PLAIN_SCALAR_STYLE;
158 48         82 $node->{data} = $special{ $node->{value} };
159 48         143 return 1;
160             }
161 83 100       334 if (0.0 + $node->{value} ne $node->{value}) {
162 4         10 return 0;
163             }
164 79 100 66     364 if (int($node->{value}) eq $node->{value} and not $node->{value} =~ m/\./) {
165 35         56 $node->{value} .= '.0';
166             }
167 79         159 $node->{style} = YAML_PLAIN_SCALAR_STYLE;
168 79         219 $node->{data} = "$node->{value}";
169 79         238 return 1;
170             }
171              
172             sub represent_bool {
173 66     66 1 116 my ($rep, $node) = @_;
174 66 100       292 my $string = $node->{value} ? 'true' : 'false';
175 66         561 $node->{style} = YAML_PLAIN_SCALAR_STYLE;
176 66         93 @{ $node->{items} } = $string;
  66         469  
177 66         121 $node->{data} = $string;
178 66         232 return 1;
179             }
180              
181             1;
182              
183             __END__
184              
185             =pod
186              
187             =encoding utf-8
188              
189             =head1 NAME
190              
191             YAML::PP::Schema::JSON - YAML 1.2 JSON Schema
192              
193             =head1 SYNOPSIS
194              
195             my $yp = YAML::PP->new( schema => ['JSON'] );
196             my $yp = YAML::PP->new( schema => [qw/ JSON empty=str /] );
197             my $yp = YAML::PP->new( schema => [qw/ JSON empty=null /] );
198              
199             =head1 DESCRIPTION
200              
201             With this schema, the resolution of plain values will work like in JSON.
202             Everything that matches a special value will be loaded as such, other plain
203             scalars will be loaded as strings.
204              
205             Note that this is different from the official YAML 1.2 JSON Schema, where all
206             strings have to be quoted.
207              
208             Here you can see all Schemas and examples implemented by YAML::PP:
209             L<https://perlpunk.github.io/YAML-PP-p5/schemas.html>
210              
211             Official Schema: L<https://yaml.org/spec/1.2/spec.html#id2803231>
212              
213             =head1 CONFIGURATION
214              
215             The official YAML 1.2 JSON Schema wants all strings to be quoted.
216             YAML::PP currently does not require that (it might do this optionally in
217             the future).
218              
219             That means, there are no empty nodes allowed in the official schema. Example:
220              
221             ---
222             key:
223              
224             The default behaviour of YAML::PP::Schema::JSON is to return an empty string,
225             so it would be equivalent to:
226              
227             ---
228             key: ''
229              
230             You can configure it to resolve this as C<undef>:
231              
232             my $yp = YAML::PP->new( schema => [qw/ JSON empty=null /] );
233              
234             This way it is equivalent to:
235              
236             ---
237             key: null
238              
239             The default is:
240              
241             my $yp = YAML::PP->new( schema => [qw/ JSON empty=str /] );
242              
243             =head1 METHODS
244              
245             =over
246              
247             =item register
248              
249             Called by YAML::PP::Schema
250              
251             =item represent_bool, represent_float, represent_int, represent_literal, represent_undef
252              
253             Functions to represent the several node types.
254              
255             represent_bool($representer, $node);
256              
257             =back
258              
259             =cut