First, Perl does not have types. It does not distinguish between strings and numbers (on the outside).
Furthermore, it does not make a difference between numbers and strings on this level. The numerical context and string context matters if you check what's greater or less than. Consider this:
my $foo = 200;
my $bar = 99;
print $foo > $bar ? $foo : $bar;
Obviously it will print 200, because 200 is numerically larger than 99.
my $foo = 200;
my $bar = 99;
print $foo gt $bar ? $foo : $bar;
But this will print 99, because 9 is alphanumerically (as in string) greater than 2. It compared the numbers of the code points for the characters.
But if all you want to do is check for inequality, the ne operator is fine. Even when you are not sure whether there are things other than numbers in your input.
foreach my $key ( keys(%$hash1) ) {
if ($hash1->{$key} ne $hash2->{$key}) {
print($key);
}
}
eq (and ne) are smart enough to see if a number was initially a string or a number without quotes, because the internal representation of those differs.
Warning, technical details ahead.
Scalar values are saved in _SV_s. These in terms can contain different things. There's a special internal type for simple integers called IV, and also one called PV for strings. Perl internally converts between those two as needed when you use numbers inside of strings, or vise versa.
You can get some debugging information about the internal representation of data with Dump from Devel::Peek.
use Devel::Peek;
Dump("01");
Dump(01);
This will output:
SV = PV(0x19560d0) at 0x19327d0
REFCNT = 1
FLAGS = (POK,READONLY,IsCOW,pPOK)
PV = 0x1c94fd0 "01"\0
CUR = 2
LEN = 10
COW_REFCNT = 0
SV = IV(0x19739b0) at 0x19739c0
REFCNT = 1
FLAGS = (IOK,READONLY,pIOK)
IV = 1
As you can see, the first one is a string, and the second one is a number.
But if we do this
print "01" eq 01;
there is no output, because the 01 is an integer and will be converted to "1" for comparison. Since the 0 of "01" is not equal to 1, nothing gets printed.
If the values of your data structures are more complex, you need to walk the structure. Each type of element needs to have its own handling. There could be array references, hash references, scalar references, scalars, glob references, dualvars and so on. There might be objects that you want to treat specially.
I suggest taking a look at how Test::Deep implements this. If you decide to use it in production code (and not a unit test), you can use Test::Deep::NoTest.