end0tknr's kipple - web写経開発

太宰府天満宮の狛犬って、妙にカワイイ

JSON for perl ? における 文字列(string) or 数値(numeric, integer)の判別方法

サーバからjsonを受け取った javascript が、 数値(例:2)を期待し厳密等価演算子 (===)でvalidateしたところ、 文字列(例:"2")だった為、false となったことがきっかけです。

perl製サーバアプリとしては、$val += 0 により、数値化( numfy )しましたが、 数値と文字列を区別なく扱えるperlで、JSON.pm は、 どのようにして、文字列(string) or 数値(numeric, integer)の判別しているのでしょう?

JSON for perl は、文字列 or 数値を判別し、json

#!/usr/local/bin/perl
use strict;
use JSON;

main();
sub main {
    print JSON::encode_json({val=>'10'}),"\n";
    print JSON::encode_json({val=>10}),"\n";
}
1;

↑こう書くと、↓こう出力されます

$ ./test_encode_json.pl 
{"val":"10"}
{"val":10}

JSON from perl のドキュメントには文字列 or 数値を判別できる旨が記載

http://search.cpan.org/perldoc?JSON に次のように記載されていますが、 一体、どのように実装されているのでしょう?

number A JSON number becomes either an integer, numeric (floating point) or string scalar in perl, depending on its range and any fractional parts. On the Perl level, there is no difference between those as Perl handles all the conversion details, but an integer may take slightly less memory and might represent more values exactly than floating point numbers.

If the number consists of digits only, JSON will try to represent it as an integer value. If that fails, it will try to represent it as a numeric (floating point) value if that is possible without loss of precision. Otherwise it will preserve the number as a string value (in which case you lose roundtripping ability, as the JSON number will be re-encoded to a JSON string).

JSONのバックエンドにある JSON::PP は B moduleを利用

JSONは、バックエンドに JSON::XSまたはJSON::PP を使用しますが JSON::PPはB moduleを利用して、数値or文字列を判別しています。

具体的には、B for perlを利用して、 B::SVp_IOK , B::SVp_NOK , B::SVp_POK とビット演算することで判別しています。

perlスカラー値には、整数 (IV)、符号なし整数 (UV)、 倍精度 (NV)、文字列 (PV)、他スカラ (SV) があります。

sub value_to_json {
    my ($self, $value) = @_;

    return 'null' if(!defined $value);

    my $b_obj = B::svref_2object(\$value);  # for round trip problem
    my $flags = $b_obj->FLAGS;
    ######## HERE !!!! ########
    return $value # as is 
        if $flags & ( B::SVp_IOK | B::SVp_NOK ) and !( $flags & B::SVp_POK ); # SvTYPE is IV or NV?

    my $type = ref($value);

    if(!$type){
        return string_to_json($self, $value);
    }
    elsif( blessed($value) and  $value->isa('JSON::PP::Boolean') ){
        return $$value == 1 ? 'true' : 'false';
    }
    elsif ($type) {
        if ((overload::StrVal($value) =~ /=(\w+)/)[0]) {
            return $self->value_to_json("$value");
        }

        if ($type eq 'SCALAR' and defined $$value) {
            return   $$value eq '1' ? 'true'
                   : $$value eq '0' ? 'false'
                   : $self->{PROPS}->[ P_ALLOW_UNKNOWN ] ? 'null'
                   : encode_error("cannot encode reference to scalar");
        }

         if ( $self->{PROPS}->[ P_ALLOW_UNKNOWN ] ) {
             return 'null';
         }
         else {
             if ( $type eq 'SCALAR' or $type eq 'REF' ) {
                encode_error("cannot encode reference to scalar");
             }
             else {
                encode_error("encountered $value, but JSON can only represent references to arrays or hashes");
             }
         }

    }
    else {
        return $self->{fallback}->($value)
             if ($self->{fallback} and ref($self->{fallback}) eq 'CODE');
        return 'null';
    }

}

http://search.cpan.org/perldoc?JSON%3A%3APP

自分で、文字列(string) or 数値(numeric, integer)を判別するには?

次のように書けはOKです

#!/usr/local/bin/perl
use strict;
use B ();

main();
sub main {

    for my $value (10, "10", 10.0,  10.1, "10"){
        my $b_obj = B::svref_2object(\$value);
        my $flags = $b_obj->FLAGS;
        my $comp_1 = ($flags & ( B::SVp_IOK | B::SVp_NOK )),"\n";
        my $comp_2 =  ( $flags & B::SVp_POK ),"\n";
        if( $comp_1 and not $comp_2){
            print "$value is NUMERIC\n";
        } else {
            print "$value is            STRING\n";
        }
    }
}