File Coverage

blib/lib/JSON/Schema/Generate.pm
Criterion Covered Total %
statement 121 127 95.2
branch 50 58 86.2
condition 13 15 86.6
subroutine 18 18 100.0
pod 3 3 100.0
total 205 221 92.7


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