In the following code, I have a class tied to a hash. In the FETCH
function, I am printing the JSON
encoding of the key:
package Example::Tie;
use JSON;
my $json = JSON->new();
sub TIEHASH {
my ($pkg,@list) = @_;
bless { @list }, $pkg;
}
sub FETCH {
my ($tied,$key) = @_;
return $json->encode({key => $key});
}
package Example;
sub new {
my ($pkg,@list) = @_;
my $self = {};
tie %$self, 'Example::Tie', @list;
bless $self, $pkg;
}
package main;
my $exp = Example->new();
print($exp->{0} . "\n");
I get the following output:
{"key":"0"}
This results in 0
being encoded as a string. Is there a way to encode it into a number instead?
print($exp->{0} . "\n"); # this should print {"key":0}
print($exp->{'0'} . "\n"); # this should print {"key":"0"}
Since there is no real concept of string or number in Perl, but only the scalar, this is tricky. The JSON module tries to do it by looking at the last context the value it encodes was used in.
Simple Perl scalars (any scalar that is not a reference) are the most difficult objects to encode: this module will encode undefined scalars as JSON null values, scalars that have last been used in a string context before encoding as JSON strings, and anything else as number value:
# dump as number encode_json [2] # yields [2] encode_json [-3.0e17] # yields [-3e+17] my $value = 5; encode_json [$value] # yields [5] # used as string, so dump as string print $value; encode_json [$value] # yields ["5"] # undef becomes null encode_json [undef] # yields [null]
Your code in FETCH
does not do this specifically. So it has to be somewhere else.
My guess is that Perl's automatic quoting for hash keys is the culprit here.
$exp->{0}; # this should print {"key":0}
$exp->{'0'}; # this should print {"key":"0"}
These two expressions are equivalent. Perl will automatically treat things inside of the {}
for hash (ref) elements as quoted and they become strings. Because that's easily forgotten, there is best practice to always use single quotes ''
.
Perldata says (emphasis mine):
Hashes are unordered collections of scalar values indexed by their associated string key.
The idea is that there are no numerical keys for hashes. If there are numerical keys, they can be ordered, and then you have an array.
You can get further proof for this by calling your FETCH
directly with an unquoted number as the arg.
Example::Tie->FETCH(1);
This will result in
{"key":1}
I therefore conclude that using tie
with the JSON module what you want to do is not possible, unless you explicitly try to force it back to be a number. There is an example in the JSON module's documentation.
You can force the type to be a number by numifying it:
my $x = "3"; # some variable containing a string $x += 0; # numify it, ensuring it will be dumped as a number $x *= 1; # same thing, the choice is yours.
Basically, @simbabque's answer is spot on. By the time, your FETCH
gets the argument list, the 0
in $exp->{0}
has already been stringified because hash keys are always strings.
Of course, you are going to have issues if you add 0
to every argument to fetch indiscriminately. Below, I use Scalar::Util::looks_like_number to distinguish between numbers and strings, but, of course, this will not work if you try it with "0"
. That will also get converted to 0
.
use strict; use warnings;
package Example::Tie;
use JSON;
use Scalar::Util qw( looks_like_number );
my $json = JSON->new;
sub TIEHASH {
my $pkg = shift;
bless { @_ } => $pkg;
}
sub FETCH {
my $tied = shift;
$json->encode({key => looks_like_number($_[0]) ? 0 + $_[0] : $_[0]})
}
package Example;
sub new {
my $pkg = shift;
my $self = {};
tie %$self, 'Example::Tie', @_;
bless $self => $pkg;
}
package main;
my $exp = Example->new;
print "$_\n" for map $exp->{$_}, 0, 'a';
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With