File Coverage

blib/lib/JSON/TypeInference.pm
Criterion Covered Total %
statement 83 83 100.0
branch 8 8 100.0
condition 3 4 75.0
subroutine 23 23 100.0
pod 1 1 100.0
total 118 119 99.1


line stmt bran cond sub pod time code
1             package JSON::TypeInference;
2 2     2   5481 use 5.008001;
  2         8  
3 2     2   10 use strict;
  2         3  
  2         45  
4 2     2   19 use warnings;
  2         4  
  2         101  
5              
6             our $VERSION = "1.0.0";
7              
8 2     2   9 use List::Util qw(first);
  2         3  
  2         212  
9 2     2   1570 use List::UtilsBy qw(partition_by sort_by);
  2         3466  
  2         137  
10              
11 2     2   557 use JSON::TypeInference::Type::Array;
  2         3  
  2         53  
12 2     2   529 use JSON::TypeInference::Type::Boolean;
  2         6  
  2         53  
13 2     2   1088 use JSON::TypeInference::Type::Maybe;
  2         5  
  2         57  
14 2     2   559 use JSON::TypeInference::Type::Null;
  2         5  
  2         56  
15 2     2   545 use JSON::TypeInference::Type::Number;
  2         6  
  2         48  
16 2     2   555 use JSON::TypeInference::Type::Object;
  2         4  
  2         49  
17 2     2   548 use JSON::TypeInference::Type::String;
  2         3  
  2         47  
18 2     2   569 use JSON::TypeInference::Type::Union;
  2         3  
  2         57  
19 2     2   1082 use JSON::TypeInference::Type::Unknown;
  2         6  
  2         106  
20              
21             use constant ENTITY_TYPE_CLASSES => [
22 2         5   map { join '::', 'JSON::TypeInference::Type', $_ } qw( Array Boolean Null Number Object String )
  12         1215  
23 2     2   10 ];
  2         4  
24              
25             # [Any] => Type
26             sub infer {
27 19     19 1 21794   my ($class, $dataset) = @_;
28 19     38   94   my $dataset_by_type = { partition_by { _infer_type_for($_) } @$dataset };
  38         261  
29 19         207   my $possible_type_classes = [ keys %$dataset_by_type ];
30               my $candidate_types = [ map {
31 19         44     my $type_class = $_;
  22         27  
32 22 100       60     if ($type_class eq 'JSON::TypeInference::Type::Array') {
    100          
33 2         4       my $dataset = $dataset_by_type->{$type_class};
34 2         5       $class->_infer_array_element_types($dataset);
35                 } elsif ($type_class eq 'JSON::TypeInference::Type::Object') {
36 2         5       my $dataset = $dataset_by_type->{$type_class}; # ArrayRef[HashRef[Str, Any]]
37 2         5       $class->_infer_object_property_types($dataset);
38                 } else {
39 18   50     105       ($type_class // 'JSON::TypeInference::Type::Unknown')->new;
40                 }
41               } @$possible_type_classes ];
42              
43 19 100       75   if (JSON::TypeInference::Type::Maybe->looks_like_maybe($candidate_types)) {
    100          
44 1     2   8     my $entity_type = first { ! $_->isa('JSON::TypeInference::Type::Null') } @$candidate_types;
  2         9  
45 1         8     return JSON::TypeInference::Type::Maybe->new($entity_type);
46               } elsif (scalar(@$candidate_types) > 1) {
47 2     4   11     return JSON::TypeInference::Type::Union->new(sort_by { $_->name } @$candidate_types);
  4         27  
48               } else {
49 16         88     return $candidate_types->[0];
50               }
51             }
52              
53             # ArrayRef[ArrayRef[Any]] => JSON::TypeInference::Type::Array
54             sub _infer_array_element_types {
55 2     2   5   my ($class, $dataset) = @_;
56 2         4   my $elements = [ map { @$_ } @$dataset ];
  3         7  
57 2         9   my $element_type = $class->infer($elements);
58 2         9   return JSON::TypeInference::Type::Array->new($element_type);
59             }
60              
61             # ArrayRef[HashRef[Str, Any]] => JSON::TypeInference::Type::Object
62             sub _infer_object_property_types {
63 2     2   4   my ($class, $dataset) = @_;
64 2         4   my $keys = [ map { keys %$_ } @$dataset ]; # ArrayRef[Str]
  4         38  
65               my $dataset_by_prop = { map {
66 2         5     my $prop = $_;
  7         11  
67 7         10     ($prop => [ map { $_->{$prop} } @$dataset ])
  14         32  
68               } @$keys }; # HashRef[Str, ArrayRef[Str]]
69 2         7   my $prop_types = { map { ($_ => $class->infer($dataset_by_prop->{$_})) } @$keys };
  7         19  
70 2         10   return JSON::TypeInference::Type::Object->new($prop_types);
71             }
72              
73             # Any => Type
74             sub _infer_type_for {
75 38     38   52   my ($data) = @_;
76 38   100 140   104   return (first { $_->accepts($data) } @{ENTITY_TYPE_CLASSES()}) // 'JSON::TypeInference::Type::Unknown';
  140         612  
  38         143  
77             }
78              
79             1;
80             __END__
81            
82             =encoding utf-8
83            
84             =head1 NAME
85            
86             JSON::TypeInference - Inferencing JSON types from given Perl values
87            
88             =head1 SYNOPSIS
89            
90             use JSON::TypeInference;
91            
92             my $data = [
93             { name => 'yuno' },
94             { name => 'miyako' },
95             { name => 'nazuna' },
96             { name => 'nori' },
97             ];
98             my $inferred_type = JSON::TypeInference->infer($data); # object[name:string]
99            
100             =head1 DESCRIPTION
101            
102             C< JSON::TypeInference > infers the type of JSON values from the given Perl values.
103            
104             If some candidate types of the given Perl values are inferred, C< JSON::TypeInference > reports the type of it as a union type that consists of all candidate types.
105            
106             =head1 CLASS METHODS
107            
108             =over 4
109            
110             =item C<< infer($dataset: ArrayRef[Any]); # => JSON::TypeInference::Type >>
111            
112             To infer the type of JSON values from the given values.
113            
114             Return value is a instance of C< JSON::TypeInference::Type > that means the inferred JSON type.
115            
116             =back
117            
118             =head1 LICENSE
119            
120             Copyright (C) aereal.
121            
122             This library is free software; you can redistribute it and/or modify
123             it under the same terms as Perl itself.
124            
125             =head1 AUTHOR
126            
127             aereal E<lt>aereal@aereal.orgE<gt>
128            
129             =head1 SEE ALSO
130            
131             L<JSON::TypeInference::Type>
132            
133             =cut
134            
135