File Coverage

blib/lib/JSON/Schema/Generate.pm
Criterion Covered Total %
statement 122 128 95.3
branch 50 58 86.2
condition 13 15 86.6
subroutine 18 18 100.0
pod 3 3 100.0
total 206 222 92.7


line stmt bran cond sub pod time code
1             package JSON::Schema::Generate;
2 4     4   412924 use 5.006; use strict; use warnings; our $VERSION = '0.06';
  4     4   48  
  4     4   32  
  4         12  
  4         78  
  4         18  
  4         8  
  4         195  
3 4     4   2842 use Tie::IxHash; use Types::Standard qw/Str HashRef Bool/;
  4     4   12551  
  4         149  
  4         4023  
  4         418400  
  4         52  
4 4     4   6537 use Compiled::Params::OO qw/cpo/; use JSON; use Blessed::Merge;
  4     4   145241  
  4     4   46  
  4         4802  
  4         43246  
  4         29  
  4         2505  
  4         9832  
  4         900  
5              
6             our ($validate, $JSON);
7             BEGIN {
8             $validate = cpo(
9             new => {
10             id => {
11             type => Str,
12             default => sub {
13 0         0 'http://example.com/root.json'
14             }
15             },
16             title => {
17             type => Str,
18             default => sub {
19 2         53 'The Root Schema'
20             }
21             },
22             description => {
23             type => Str,
24             default => sub {
25 0         0 'The root schema is the schema that comprises the entire JSON document.'
26             }
27             },
28             schema => {
29             type => Str,
30             default => sub {
31 3         77 'http://json-schema.org/draft-07/schema#'
32             }
33             },
34             spec => {
35             type => HashRef,
36 1         22 default => sub { { } }
37             },
38             merge_examples => {
39             type => Bool,
40 3         70 default => sub { !!0 }
41             },
42             none_required => {
43             type => Bool,
44 2         60 default => sub { !!0 }
45             },
46             no_id => {
47             type => Bool,
48 3         58 default => sub { !!0 }
49             }
50             }
51 4     4   44 );
52 4         45327 $JSON = JSON->new->pretty;
53             }
54              
55             sub new {
56 3     3 1 462 my $class = shift;
57 3         38 my $args = $validate->new->(@_);
58 3         103 my $self = bless {}, $class;
59 3         14 $self->{schema} = _tie_hash();
60 3         33 $self->{schema}{'$schema'} = $args->schema;
61 3         91 $self->{schema}{'$id'} = $args->id;
62 3         55 $self->{schema}{title} = $args->title;
63 3         51 $self->{schema}{description} = $args->description;
64 3         71 $self->{$_} = $args->$_ for qw/spec merge_examples none_required no_id/;
65 3 50       19 if ($args->merge_examples) {
66 0         0 $self->{merge} = Blessed::Merge->new(
67             blessed => 0,
68             unique_array => 1,
69             unique_hash => 1
70             );
71             }
72 3         35 return $self;
73             }
74              
75             sub learn {
76 4 50   4 1 114 $_[0]->{data} = ref $_[1] ? $_[1] : $JSON->decode($_[1]);
77 4         15 $_[0]->{data_runs}++;
78 4         21 $_[0]->_build_props($_[0]->{schema}, $_[0]->{data}, '#');
79 4         29 return $_[0];
80             }
81              
82             sub generate {
83 3     3 1 18 my ($self, $struct) = @_;
84 3 100       19 $self->_handle_required($self->{schema}) unless $self->{none_required};
85 3 50       128 return $struct ? $self->{schema} : $JSON->encode($self->{schema});
86             }
87              
88             sub _handle_required {
89 30     30   480 my ($self, $schema) = @_;
90 30         158 my $total_runs = $self->{data_runs};
91 30 100       69 if ($schema->{required}) {
92 4         28 my @required;
93 4         6 for my $key (keys %{$schema->{required}}) {
  4         12  
94 25 100       107 if ($schema->{required}->{$key} == $total_runs) {
95 21         154 push @required, $key;
96             }
97             }
98 4         17 $schema->{required} = \@required;
99             }
100 30 100       228 if ($schema->{properties}) {
101 4         37 $self->_handle_required($schema->{properties}{$_}) for keys %{$schema->{properties}};
  4         16  
102             }
103 30 100       203 $self->_handle_required($schema->{items}) if $schema->{items};
104             }
105              
106             sub _build_props {
107 59     59   391 my ($self, $props, $data, $path) = @_;
108              
109 59         333 my $ref = ref $data;
110              
111 59 100 66     360 if ($ref eq 'HASH') {
    100          
    100          
    100          
    100          
    50          
112 8         25 $self->_add_type($props, 'object');
113 8         122 $self->_unique_examples($props, $data);
114 8 50       73 return if ref $props->{type};
115 8 100       68 if (!$props->{properties}) {
116 5 100       59 $props->{required} = {} unless $self->{none_required};
117 5         60 $props->{properties} = _tie_hash();
118             }
119 8         92 for my $key (sort keys %{$data}) {
  8         46  
120 46 100       162 $props->{required}->{$key}++ unless $self->{none_required};
121 46         312 my $id = $path . '/properties/' . $key;
122 46 100       135 unless ($props->{properties}{$key}) {
123 27         282 $props->{properties}{$key} = _tie_hash();
124 27         81 %{$props->{properties}{$key}} = (
125             ($self->{no_id} ? () : ('$id' => $id)),
126             title => 'The title',
127             description => 'An explanation about the purpose of this instance',
128 27 50       538 ($self->{spec}->{$key} ? %{$self->{spec}->{$key}} : ())
  2 100       7  
129             );
130             }
131 46         2437 $self->_build_props($props->{properties}{$key}, $data->{$key}, $id);
132             }
133             } elsif ($ref eq 'ARRAY') {
134 5         14 $self->_add_type($props, 'array');
135 5         65 my $id = $path . '/items';
136 5 100       14 unless ($props->{items}) {
137 3         21 $props->{items} = _tie_hash();
138 3 50       48 $props->{items}{'$id'} = $id unless $self->{no_id};
139 3         73 $props->{items}{title} = 'The Items Schema';
140 3         83 $props->{items}{description} = 'An explanation about the purpose of this instance.';
141             }
142             map {
143 9         26 $self->_build_props($props->{items}, $_, $id);
144 5         71 } @{$data}
  5         10  
145             } elsif (! defined $data) {
146 2         6 $self->_add_type($props, 'null');
147 2         12 $self->_unique_examples($props, undef);
148             } elsif ($ref eq 'SCALAR' || $ref =~ m/Boolean$/) {
149 6         36 $self->_add_type($props, 'boolean');
150 6         83 $self->_unique_examples($props, \1, \0);
151             } elsif ($data =~ m/^\d+$/) {
152 9         29 $self->_add_type($props, 'integer');
153 9         117 $self->_unique_examples($props, $data);
154             } elsif ($data =~ m/^\d+\.\d+$/) {
155 0         0 $self->_add_type($props, 'number');
156 0         0 $self->_unique_examples($props, $data);
157             # } elsif ($data =~ m/\d{4}\-\d{2}\-\d{2}T\d{2}\:\d{2}\:\d{2}\+\d{2}\:\d{2}/) {
158             # $self->_add_type($props, 'date-time');
159             # $self->_unique_examples($props, $data);
160             } else {
161 29         74 $self->_add_type($props, 'string');
162 29         363 $self->_unique_examples($props, $data);
163             }
164              
165 59         407 return $props;
166             }
167              
168             sub _tie_hash {
169 38     38   60 my %properties;
170 38         126 tie %properties, 'Tie::IxHash';
171 38         624 return \%properties;
172             }
173              
174             sub _add_type {
175 59     59   114 my ($self, $props, $type) = @_;
176 59 100       170 if (!$props->{type}) {
    100          
177 33         237 $props->{type} = $type;
178             } elsif ($props->{type} ne $type) {
179 6 100       77 $props->{type} = [ $props->{type} ] unless ref $props->{type};
180 6 100       71 push @{$props->{type}}, $type unless grep { $type eq $_ } @{$props->{type}};
  4         11  
  15         59  
  6         17  
181             }
182             }
183              
184             sub _unique_examples {
185 54     54   117 my ($self, $props, @examples) = @_;
186 54         101 for my $example (@examples) {
187 4     4   2891 use Data::Dumper;
  4         27844  
  4         691  
188 60 50 100     263 if ((ref($example) || 'SCALAR') ne 'SCALAR' && $props->{examples} && $self->{merge_examples}) {
      100        
      66        
189 0         0 $props->{examples}->[0] = $self->{merge}->merge($props->{examples}->[0], $example);
190             } else {
191 60 100 100     149 unless (grep { ($_//"") eq ($example//"") } @{$props->{examples}}) {
  49   100     324  
  60         168  
192 55         697 push @{$props->{examples}}, $example;
  55         141  
193             }
194             }
195             }
196             }
197              
198             1;
199              
200             __END__
201              
202             =head1 NAME
203              
204             JSON::Schema::Generate - Generate JSON Schemas from data!
205              
206             =head1 VERSION
207              
208             Version 0.06
209              
210             =cut
211              
212             =head1 SYNOPSIS
213              
214             use JSON::Schema::Generate;
215              
216             my $data = '{
217             "checked": false,
218             "dimensions": {
219             "width": 10,
220             "height": 10
221             },
222             "id": 1,
223             "name": "Opposite",
224             "distance": 435,
225             "tags": [
226             { "date-time": "2020-02-24T10:00:00+00:00" }
227             ]
228             }';
229              
230             my $schema = JSON::Schema::Generate->new(
231             id => 'https://flat.world-wide.world/schema',
232             title => '...'
233             description => '...',
234             spec => {
235             name => {
236             title => '...',
237             description => '...'
238             },
239             ...
240             }
241             )->learn($data)->generate;
242              
243             use JSON::Schema;
244             my $validator = JSON::Schema->new($schema);
245             my $result = $validator->validate($data);
246              
247             ...
248              
249             my $schema = JSON::Schema::Generate->new(
250             no_id => 1
251             )->learn($data)->generate(1);
252              
253             use JSON::Schema::Draft201909;
254              
255             $js = JSON::Schema::Draft201909->new;
256             $result = $js->evaluate_json_string($data, $schema);
257              
258              
259             =head1 DESCRIPTION
260              
261             JSON::Schema::Generate is a tool allowing you to derive JSON Schemas from a set of data.
262              
263             =head1 SUBROUTINES/METHODS
264              
265             =head2 new
266              
267             Instantiate a new JSON::Schema::Generate Object
268              
269             my $schema = JSON::Schema->new(
270             ...
271             );
272              
273             It accepts the following parameters:
274              
275             =over
276              
277             =item id
278              
279             The root $id of the schema. default: http://example.com/root.json
280              
281             =item title
282              
283             The root title of the schema. default: The Root Schema
284              
285             =item description
286              
287             The root description of the schema. default: The root schema is the schema that comprises the entire JSON document.
288              
289             =item schema
290              
291             The root schema version. default: 'http://json-schema.org/draft-07/schema#'
292              
293             =item spec
294              
295             A mapping hash reference that represent a key inside of the passed data and a value that contains additional metadata to be added to the schema. default: {}
296              
297             =item merge_examples
298              
299             Merge all learn data examples into a single example. default: false
300              
301             =item none_required
302              
303             Do not analyse required keys in properties. default: false.
304              
305             =item no_id
306              
307             Do not add $id(s) to properties and items. default: false.
308              
309             =back
310              
311             =head2 learn
312              
313             Accepts a JSON string, Hashref or ArrayRef that it will traverse to build a valid JSON schema. Learn can be chained allowing you to build a schema from multiple data sources.
314              
315             $schema->learn($data1)->learn($data2)->learn($data3);
316              
317             =cut
318              
319             =head2 generate
320              
321             Compiles the learned data and generates the final JSON schema in JSON format.
322              
323             $schema->generate();
324              
325             Optionally you can pass a boolean (true value) which will return the schema as a perl struct.
326              
327             $schema->generate(1)
328              
329             =cut
330              
331             =head1 EXAMPLE
332              
333             use JSON::Schema::Generate;
334              
335             my $data1 = '{
336             "links" : {
337             "cpantesters_reports" : "http://cpantesters.org/author/L/LNATION.html",
338             "cpan_directory" : "http://cpan.org/authors/id/L/LN/LNATION",
339             "metacpan_explorer" : "https://explorer.metacpan.org/?url=/author/LNATION",
340             "cpantesters_matrix" : "http://matrix.cpantesters.org/?author=LNATION",
341             "cpants" : "http://cpants.cpanauthors.org/author/LNATION",
342             "backpan_directory" : "https://cpan.metacpan.org/authors/id/L/LN/LNATION"
343             },
344             "city" : "PLUTO",
345             "updated" : "2020-02-16T16:43:51",
346             "region" : "GHANDI",
347             "is_pause_custodial_account" : false,
348             "country" : "WO",
349             "website" : [
350             "https://www.lnation.org"
351             ],
352             "asciiname" : "Robert Acock",
353             "gravatar_url" : "https://secure.gravatar.com/avatar/8e509558181e1d2a0d3a5b55dec0b108?s=130&d=identicon",
354             "pauseid" : "LNATION",
355             "email" : [
356             "lnation@cpan.org"
357             ],
358             "release_count" : {
359             "cpan" : 378,
360             "backpan-only" : 34,
361             "latest" : 114
362             },
363             "name" : "Robert Acock"
364             }';
365              
366             my $data2 = '{
367             "asciiname" : "",
368             "release_count" : {
369             "latest" : 56,
370             "backpan-only" : 358,
371             "cpan" : 190
372             },
373             "name" : "Damian Conway",
374             "email" : "damian@conway.org",
375             "is_pause_custodial_account" : false,
376             "gravatar_url" : "https://secure.gravatar.com/avatar/3d4a6a089964a744d7b3cf2415f81951?s=130&d=identicon",
377             "links" : {
378             "cpants" : "http://cpants.cpanauthors.org/author/DCONWAY",
379             "cpan_directory" : "http://cpan.org/authors/id/D/DC/DCONWAY",
380             "cpantesters_matrix" : "http://matrix.cpantesters.org/?author=DCONWAY",
381             "cpantesters_reports" : "http://cpantesters.org/author/D/DCONWAY.html",
382             "backpan_directory" : "https://cpan.metacpan.org/authors/id/D/DC/DCONWAY",
383             "metacpan_explorer" : "https://explorer.metacpan.org/?url=/author/DCONWAY"
384             },
385             "pauseid" : "DCONWAY",
386             "website" : [
387             "http://damian.conway.org/"
388             ]
389             }';
390              
391             my $schema = JSON::Schema::Generate->new(
392             id => 'https://metacpan.org/author.json',
393             title => 'The CPAN Author Schema',
394             description => 'A representation of a cpan author.',
395             )->learn($data1)->learn($data2)->generate;
396              
397             Will generate the following schema:
398            
399             {
400             "$schema" : "http://json-schema.org/draft-07/schema#",
401             "$id" : "https://metacpan.org/author.json",
402             "title" : "The CPAN Author Schema",
403             "description" : "A representation of a cpan author.",
404             "type" : "object",
405             "examples" : [
406             {
407             "region" : "GHANDI",
408             "gravatar_url" : "https://secure.gravatar.com/avatar/8e509558181e1d2a0d3a5b55dec0b108?s=130&d=identicon",
409             "is_pause_custodial_account" : false,
410             "asciiname" : "Robert Acock",
411             "release_count" : {
412             "backpan-only" : 34,
413             "latest" : 114,
414             "cpan" : 378
415             },
416             "country" : "WO",
417             "city" : "PLUTO",
418             "pauseid" : "LNATION",
419             "links" : {
420             "cpants" : "http://cpants.cpanauthors.org/author/LNATION",
421             "metacpan_explorer" : "https://explorer.metacpan.org/?url=/author/LNATION",
422             "cpantesters_reports" : "http://cpantesters.org/author/L/LNATION.html",
423             "cpan_directory" : "http://cpan.org/authors/id/L/LN/LNATION",
424             "cpantesters_matrix" : "http://matrix.cpantesters.org/?author=LNATION",
425             "backpan_directory" : "https://cpan.metacpan.org/authors/id/L/LN/LNATION"
426             },
427             "updated" : "2020-02-16T16:43:51",
428             "website" : [
429             "https://www.lnation.org"
430             ],
431             "name" : "Robert Acock",
432             "email" : [
433             "lnation@cpan.org"
434             ]
435             },
436             {
437             "is_pause_custodial_account" : false,
438             "gravatar_url" : "https://secure.gravatar.com/avatar/3d4a6a089964a744d7b3cf2415f81951?s=130&d=identicon",
439             "asciiname" : "",
440             "release_count" : {
441             "backpan-only" : 358,
442             "latest" : 56,
443             "cpan" : 190
444             },
445             "links" : {
446             "cpan_directory" : "http://cpan.org/authors/id/D/DC/DCONWAY",
447             "cpantesters_reports" : "http://cpantesters.org/author/D/DCONWAY.html",
448             "cpants" : "http://cpants.cpanauthors.org/author/DCONWAY",
449             "metacpan_explorer" : "https://explorer.metacpan.org/?url=/author/DCONWAY",
450             "backpan_directory" : "https://cpan.metacpan.org/authors/id/D/DC/DCONWAY",
451             "cpantesters_matrix" : "http://matrix.cpantesters.org/?author=DCONWAY"
452             },
453             "pauseid" : "DCONWAY",
454             "website" : [
455             "http://damian.conway.org/"
456             ],
457             "email" : "damian@conway.org",
458             "name" : "Damian Conway"
459             }
460             ],
461             "required" : [
462             "asciiname",
463             "gravatar_url",
464             "is_pause_custodial_account",
465             "release_count",
466             "pauseid",
467             "links",
468             "name",
469             "email",
470             "website"
471             ],
472             "properties" : {
473             "asciiname" : {
474             "$id" : "#/properties/asciiname",
475             "title" : "The Asciiname Schema",
476             "description" : "An explanation about the purpose of this instance.",
477             "type" : "string",
478             "examples" : [
479             "Robert Acock",
480             ""
481             ]
482             },
483             "city" : {
484             "$id" : "#/properties/city",
485             "title" : "The City Schema",
486             "description" : "An explanation about the purpose of this instance.",
487             "type" : "string",
488             "examples" : [
489             "PLUTO"
490             ]
491             },
492             "country" : {
493             "$id" : "#/properties/country",
494             "title" : "The Country Schema",
495             "description" : "An explanation about the purpose of this instance.",
496             "type" : "string",
497             "examples" : [
498             "WO"
499             ]
500             },
501             "email" : {
502             "$id" : "#/properties/email",
503             "title" : "The Email Schema",
504             "description" : "An explanation about the purpose of this instance.",
505             "type" : [
506             "array",
507             "string"
508             ],
509             "items" : {
510             "$id" : "#/properties/email/items",
511             "title" : "The Items Schema",
512             "description" : "An explanation about the purpose of this instance.",
513             "type" : "string",
514             "examples" : [
515             "lnation@cpan.org"
516             ]
517             },
518             "examples" : [
519             "damian@conway.org"
520             ]
521             },
522             "gravatar_url" : {
523             "$id" : "#/properties/gravatar_url",
524             "title" : "The Gravatar_url Schema",
525             "description" : "An explanation about the purpose of this instance.",
526             "type" : "string",
527             "examples" : [
528             "https://secure.gravatar.com/avatar/8e509558181e1d2a0d3a5b55dec0b108?s=130&d=identicon",
529             "https://secure.gravatar.com/avatar/3d4a6a089964a744d7b3cf2415f81951?s=130&d=identicon"
530             ]
531             },
532             "is_pause_custodial_account" : {
533             "$id" : "#/properties/is_pause_custodial_account",
534             "title" : "The Is_pause_custodial_account Schema",
535             "description" : "An explanation about the purpose of this instance.",
536             "type" : "boolean",
537             "examples" : [
538             true,
539             false
540             ]
541             },
542             "links" : {
543             "$id" : "#/properties/links",
544             "title" : "The Links Schema",
545             "description" : "An explanation about the purpose of this instance.",
546             "type" : "object",
547             "examples" : [
548             {
549             "cpants" : "http://cpants.cpanauthors.org/author/LNATION",
550             "metacpan_explorer" : "https://explorer.metacpan.org/?url=/author/LNATION",
551             "cpantesters_reports" : "http://cpantesters.org/author/L/LNATION.html",
552             "cpan_directory" : "http://cpan.org/authors/id/L/LN/LNATION",
553             "cpantesters_matrix" : "http://matrix.cpantesters.org/?author=LNATION",
554             "backpan_directory" : "https://cpan.metacpan.org/authors/id/L/LN/LNATION"
555             },
556             {
557             "cpan_directory" : "http://cpan.org/authors/id/D/DC/DCONWAY",
558             "cpantesters_reports" : "http://cpantesters.org/author/D/DCONWAY.html",
559             "cpants" : "http://cpants.cpanauthors.org/author/DCONWAY",
560             "metacpan_explorer" : "https://explorer.metacpan.org/?url=/author/DCONWAY",
561             "backpan_directory" : "https://cpan.metacpan.org/authors/id/D/DC/DCONWAY",
562             "cpantesters_matrix" : "http://matrix.cpantesters.org/?author=DCONWAY"
563             }
564             ],
565             "required" : [
566             "cpantesters_matrix",
567             "backpan_directory",
568             "metacpan_explorer",
569             "cpants",
570             "cpantesters_reports",
571             "cpan_directory"
572             ],
573             "properties" : {
574             "backpan_directory" : {
575             "$id" : "#/properties/links/properties/backpan_directory",
576             "title" : "The Backpan_directory Schema",
577             "description" : "An explanation about the purpose of this instance.",
578             "type" : "string",
579             "examples" : [
580             "https://cpan.metacpan.org/authors/id/L/LN/LNATION",
581             "https://cpan.metacpan.org/authors/id/D/DC/DCONWAY"
582             ]
583             },
584             "cpan_directory" : {
585             "$id" : "#/properties/links/properties/cpan_directory",
586             "title" : "The Cpan_directory Schema",
587             "description" : "An explanation about the purpose of this instance.",
588             "type" : "string",
589             "examples" : [
590             "http://cpan.org/authors/id/L/LN/LNATION",
591             "http://cpan.org/authors/id/D/DC/DCONWAY"
592             ]
593             },
594             "cpantesters_matrix" : {
595             "$id" : "#/properties/links/properties/cpantesters_matrix",
596             "title" : "The Cpantesters_matrix Schema",
597             "description" : "An explanation about the purpose of this instance.",
598             "type" : "string",
599             "examples" : [
600             "http://matrix.cpantesters.org/?author=LNATION",
601             "http://matrix.cpantesters.org/?author=DCONWAY"
602             ]
603             },
604             "cpantesters_reports" : {
605             "$id" : "#/properties/links/properties/cpantesters_reports",
606             "title" : "The Cpantesters_reports Schema",
607             "description" : "An explanation about the purpose of this instance.",
608             "type" : "string",
609             "examples" : [
610             "http://cpantesters.org/author/L/LNATION.html",
611             "http://cpantesters.org/author/D/DCONWAY.html"
612             ]
613             },
614             "cpants" : {
615             "$id" : "#/properties/links/properties/cpants",
616             "title" : "The Cpants Schema",
617             "description" : "An explanation about the purpose of this instance.",
618             "type" : "string",
619             "examples" : [
620             "http://cpants.cpanauthors.org/author/LNATION",
621             "http://cpants.cpanauthors.org/author/DCONWAY"
622             ]
623             },
624             "metacpan_explorer" : {
625             "$id" : "#/properties/links/properties/metacpan_explorer",
626             "title" : "The Metacpan_explorer Schema",
627             "description" : "An explanation about the purpose of this instance.",
628             "type" : "string",
629             "examples" : [
630             "https://explorer.metacpan.org/?url=/author/LNATION",
631             "https://explorer.metacpan.org/?url=/author/DCONWAY"
632             ]
633             }
634             }
635             },
636             "name" : {
637             "$id" : "#/properties/name",
638             "title" : "The Name Schema",
639             "description" : "An explanation about the purpose of this instance.",
640             "type" : "string",
641             "examples" : [
642             "Robert Acock",
643             "Damian Conway"
644             ]
645             },
646             "pauseid" : {
647             "$id" : "#/properties/pauseid",
648             "title" : "The Pauseid Schema",
649             "description" : "An explanation about the purpose of this instance.",
650             "type" : "string",
651             "examples" : [
652             "LNATION",
653             "DCONWAY"
654             ]
655             },
656             "region" : {
657             "$id" : "#/properties/region",
658             "title" : "The Region Schema",
659             "description" : "An explanation about the purpose of this instance.",
660             "type" : "string",
661             "examples" : [
662             "GHANDI"
663             ]
664             },
665             "release_count" : {
666             "$id" : "#/properties/release_count",
667             "title" : "The Release_count Schema",
668             "description" : "An explanation about the purpose of this instance.",
669             "type" : "object",
670             "examples" : [
671             {
672             "backpan-only" : 34,
673             "latest" : 114,
674             "cpan" : 378
675             },
676             {
677             "backpan-only" : 358,
678             "latest" : 56,
679             "cpan" : 190
680             }
681             ],
682             "required" : [
683             "latest",
684             "backpan-only",
685             "cpan"
686             ],
687             "properties" : {
688             "backpan-only" : {
689             "$id" : "#/properties/release_count/properties/backpan-only",
690             "title" : "The Backpan-only Schema",
691             "description" : "An explanation about the purpose of this instance.",
692             "type" : "integer",
693             "examples" : [
694             34,
695             358
696             ]
697             },
698             "cpan" : {
699             "$id" : "#/properties/release_count/properties/cpan",
700             "title" : "The Cpan Schema",
701             "description" : "An explanation about the purpose of this instance.",
702             "type" : "integer",
703             "examples" : [
704             378,
705             190
706             ]
707             },
708             "latest" : {
709             "$id" : "#/properties/release_count/properties/latest",
710             "title" : "The Latest Schema",
711             "description" : "An explanation about the purpose of this instance.",
712             "type" : "integer",
713             "examples" : [
714             114,
715             56
716             ]
717             }
718             }
719             },
720             "updated" : {
721             "$id" : "#/properties/updated",
722             "title" : "The Updated Schema",
723             "description" : "An explanation about the purpose of this instance.",
724             "type" : "string",
725             "examples" : [
726             "2020-02-16T16:43:51"
727             ]
728             },
729             "website" : {
730             "$id" : "#/properties/website",
731             "title" : "The Website Schema",
732             "description" : "An explanation about the purpose of this instance.",
733             "type" : "array",
734             "items" : {
735             "$id" : "#/properties/website/items",
736             "title" : "The Items Schema",
737             "description" : "An explanation about the purpose of this instance.",
738             "type" : "string",
739             "examples" : [
740             "https://www.lnation.org",
741             "http://damian.conway.org/"
742             ]
743             }
744             }
745             }
746             }
747              
748             =head1 AUTHOR
749              
750             LNATION, C<< <email at lnation.org> >>
751              
752             =head1 BUGS
753              
754             Please report any bugs or feature requests to C<bug-json-schema-generate at rt.cpan.org>, or through
755             the web interface at L<https://rt.cpan.org/NoAuth/ReportBug.html?Queue=JSON-Schema-Generate>. I will be notified, and then you'll
756             automatically be notified of progress on your bug as I make changes.
757              
758             =head1 SUPPORT
759              
760             You can find documentation for this module with the perldoc command.
761              
762             perldoc JSON::Schema::Generate
763              
764             You can also look for information at:
765              
766             =over 4
767              
768             =item * RT: CPAN's request tracker (report bugs here)
769              
770             L<https://rt.cpan.org/NoAuth/Bugs.html?Dist=JSON-Schema-Generate>
771              
772             =item * AnnoCPAN: Annotated CPAN documentation
773              
774             L<http://annocpan.org/dist/JSON-Schema-Generate>
775              
776             =item * CPAN Ratings
777              
778             L<https://cpanratings.perl.org/d/JSON-Schema-Generate>
779              
780             =item * Search CPAN
781              
782             L<https://metacpan.org/release/JSON-Schema-Generate>
783              
784             =back
785              
786             =head1 ACKNOWLEDGEMENTS
787              
788             =head1 LICENSE AND COPYRIGHT
789              
790             This software is Copyright (c) 2020 by LNATION.
791              
792             This is free software, licensed under:
793              
794             The Artistic License 2.0 (GPL Compatible)
795              
796             =cut
797              
798             1; # End of JSON::Schema::Generate