File Coverage

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


line stmt bran cond sub pod time code
1             package JSON::TypeInference;
2 2     2   3990 use 5.008001;
  2         7  
3 2     2   9 use strict;
  2         4  
  2         44  
4 2     2   19 use warnings;
  2         3  
  2         98  
5              
6             our $VERSION = "1.0.1";
7              
8 2     2   11 use List::Util qw(first);
  2         9  
  2         212  
9 2     2   1623 use List::UtilsBy qw(partition_by sort_by);
  2         3368  
  2         171  
10              
11 2     2   583 use JSON::TypeInference::Type::Array;
  2         4  
  2         52  
12 2     2   537 use JSON::TypeInference::Type::Boolean;
  2         7  
  2         55  
13 2     2   1111 use JSON::TypeInference::Type::Maybe;
  2         4  
  2         58  
14 2     2   528 use JSON::TypeInference::Type::Null;
  2         5  
  2         48  
15 2     2   572 use JSON::TypeInference::Type::Number;
  2         5  
  2         48  
16 2     2   533 use JSON::TypeInference::Type::Object;
  2         3  
  2         48  
17 2     2   520 use JSON::TypeInference::Type::String;
  2         4  
  2         47  
18 2     2   523 use JSON::TypeInference::Type::Union;
  2         3  
  2         48  
19 2     2   1117 use JSON::TypeInference::Type::Unknown;
  2         4  
  2         101  
20              
21             use constant ENTITY_TYPE_CLASSES => [
22 2         5   map { join '::', 'JSON::TypeInference::Type', $_ } qw( Array Boolean Null Number Object String )
  12         1268  
23 2     2   11 ];
  2         3  
24              
25             # [Any] => Type
26             sub infer {
27 22     22 1 24808   my ($class, $dataset) = @_;
28 22     40   97   my $dataset_by_type = { partition_by { _infer_type_for($_) } @$dataset };
  40         262  
29 22         204   my $possible_type_classes = [ keys %$dataset_by_type ];
30               my $candidate_types = [ map {
31 22         43     my $type_class = $_;
  24         30  
32 24 100       65     if ($type_class eq 'JSON::TypeInference::Type::Array') {
    100          
33 3         6       my $dataset = $dataset_by_type->{$type_class};
34 3         10       $class->_infer_array_element_types($dataset);
35                 } elsif ($type_class eq 'JSON::TypeInference::Type::Object') {
36 3         6       my $dataset = $dataset_by_type->{$type_class}; # ArrayRef[HashRef[Str, Any]]
37 3         9       $class->_infer_object_property_types($dataset);
38                 } else {
39 18         83       $type_class->new;
40                 }
41               } @$possible_type_classes ];
42              
43 22 100       82   if (JSON::TypeInference::Type::Maybe->looks_like_maybe($candidate_types)) {
    100          
44 1     2   6     my $entity_type = first { ! $_->isa('JSON::TypeInference::Type::Null') } @$candidate_types;
  2         7  
45 1         6     return JSON::TypeInference::Type::Maybe->new($entity_type);
46               } elsif (scalar(@$candidate_types) > 1) {
47 2     4   12     return JSON::TypeInference::Type::Union->new(sort_by { $_->name } @$candidate_types);
  4         25  
48               } else {
49 19   66     139     return $candidate_types->[0] // JSON::TypeInference::Type::Unknown->new;
50               }
51             }
52              
53             # ArrayRef[ArrayRef[Any]] => JSON::TypeInference::Type::Array
54             sub _infer_array_element_types {
55 3     3   5   my ($class, $dataset) = @_;
56 3         4   my $elements = [ map { @$_ } @$dataset ];
  4         9  
57 3         12   my $element_type = $class->infer($elements);
58 3         12   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 3     3   4   my ($class, $dataset) = @_;
64 3         7   my $keys = [ map { keys %$_ } @$dataset ]; # ArrayRef[Str]
  5         14  
65               my $dataset_by_prop = { map {
66 3         7     my $prop = $_;
  7         8  
67 7         13     ($prop => [ map { $_->{$prop} } @$dataset ])
  14         32  
68               } @$keys }; # HashRef[Str, ArrayRef[Str]]
69 3         8   my $prop_types = { map { ($_ => $class->infer($dataset_by_prop->{$_})) } @$keys };
  7         16  
70 3         13   return JSON::TypeInference::Type::Object->new($prop_types);
71             }
72              
73             # Any => Type
74             sub _infer_type_for {
75 40     40   56   my ($data) = @_;
76 40   100 146   108   return (first { $_->accepts($data) } @{ENTITY_TYPE_CLASSES()}) // 'JSON::TypeInference::Type::Unknown';
  146         610  
  40         117  
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