From Code to Community: Sponsoring The Perl and Raku Conference 2025 Learn more

use 5.008001;
use strict;
our $VERSION = "1.0.2";
use List::Util qw(first);
use List::UtilsBy qw(partition_by sort_by);
use constant ENTITY_TYPE_CLASSES => [
map { join '::', 'JSON::TypeInference::Type', $_ } qw( Array Boolean Null Number Object String )
];
# [Any] => Type
sub infer {
my ($class, $dataset) = @_;
my $dataset_by_type = { partition_by { _infer_type_for($_) } @$dataset };
my $possible_type_classes = [ keys %$dataset_by_type ];
my $candidate_types = [ map {
my $type_class = $_;
if ($type_class eq 'JSON::TypeInference::Type::Array') {
my $dataset = $dataset_by_type->{$type_class};
$class->_infer_array_element_types($dataset);
} elsif ($type_class eq 'JSON::TypeInference::Type::Object') {
my $dataset = $dataset_by_type->{$type_class}; # ArrayRef[HashRef[Str, Any]]
$class->_infer_object_property_types($dataset);
} else {
$type_class->new;
}
} @$possible_type_classes ];
if (JSON::TypeInference::Type::Maybe->looks_like_maybe($candidate_types)) {
my $entity_type = first { ! $_->isa('JSON::TypeInference::Type::Null') } @$candidate_types;
return JSON::TypeInference::Type::Maybe->new($entity_type);
} elsif (scalar(@$candidate_types) > 1) {
return JSON::TypeInference::Type::Union->new(sort_by { $_->name } @$candidate_types);
} else {
return $candidate_types->[0] // JSON::TypeInference::Type::Unknown->new;
}
}
# ArrayRef[ArrayRef[Any]] => JSON::TypeInference::Type::Array
sub _infer_array_element_types {
my ($class, $dataset) = @_;
my $elements = [ map { @$_ } @$dataset ];
my $element_type = $class->infer($elements);
return JSON::TypeInference::Type::Array->new($element_type);
}
# ArrayRef[HashRef[Str, Any]] => JSON::TypeInference::Type::Object
sub _infer_object_property_types {
my ($class, $dataset) = @_;
my $keys = [ map { keys %$_ } @$dataset ]; # ArrayRef[Str]
my $dataset_by_prop = { map {
my $prop = $_;
($prop => [ map { $_->{$prop} } @$dataset ])
} @$keys }; # HashRef[Str, ArrayRef[Str]]
my $prop_types = { map { ($_ => $class->infer($dataset_by_prop->{$_})) } @$keys };
return JSON::TypeInference::Type::Object->new($prop_types);
}
# Any => Type
sub _infer_type_for {
my ($data) = @_;
return (first { $_->accepts($data) } @{ENTITY_TYPE_CLASSES()}) // 'JSON::TypeInference::Type::Unknown';
}
1;
__END__
=encoding utf-8
=head1 NAME
JSON::TypeInference - Inferencing JSON types from given Perl values
=head1 SYNOPSIS
use JSON::TypeInference;
my $data = [
{ name => 'yuno' },
{ name => 'miyako' },
{ name => 'nazuna' },
{ name => 'nori' },
];
my $inferred_type = JSON::TypeInference->infer($data); # object[name:string]
=head1 DESCRIPTION
C< JSON::TypeInference > infers the type of JSON values from the given Perl values.
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.
=head1 CLASS METHODS
=over 4
=item C<< infer($dataset: ArrayRef[Any]); # => JSON::TypeInference::Type >>
To infer the type of JSON values from the given values.
Return value is a instance of C< JSON::TypeInference::Type > that means the inferred JSON type.
=back
=head1 LICENSE
Copyright (C) aereal.
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.
=head1 AUTHOR
aereal E<lt>aereal@aereal.orgE<gt>
=head1 SEE ALSO
L<JSON::TypeInference::Type>
=cut