Perl5の話題です.
JSON::XSを用いて,JSON StringをPerlのHashRefにデシリアライズする時にtrueとfalseの扱いを変えたいという話です.
デフォルト状態でJSON::XSを用いてデシリアライズすると, true
は Types::Serialiser::true
すなわち JSON::PP::Boolean
の真値として, false
は Types::Serialiser::false
すなわち JSON::PP::Boolean
の偽値として扱われます.
普通の処理であればこれで問題ないかもしれませんが,例えば「JSON StringをPerlのHashRefにしてそれを更に何らかのシリアライザに通す」といったような処理をしようとすると,JSON::PP::Boolean
のオブジェクトだと取り回しが悪い場合があります.そういった時に,true/falseを任意の値にマッピングするにはどうすれば良いか.
https://metacpan.org/pod/JSON::XS#true,-false
Types::Serialiser - simple data types for common serialisation formats - metacpan.org
やり方としては上記のドキュメントのまわりにあるように, $Types::Serialiser::true
及び $Types::Serialiser::false
を任意の値 (reference) に書き換えてやると良い.これらの変数はourで宣言されており,外から操作することができる.
use Types::Serialiser;
local $Types::Serialiser::true;
local $Types::Serialiser::false;
BEGIN {
$Types::Serialiser::true = \1;
$Types::Serialiser::false = \0;
}
use JSON::XS qw/decode_json/;
my $json = decode_json('{"true": true, "false": false}');
use Data::Dumper; warn Dumper($json);
例えばこのようにすると true
は \1
に,false
は \0
にマッピングされるようになります.
あるいは以下のように所望のオブジェクトにマッピングすることもできます;
package MyBool {
sub new {
my ($class, $val) = @_;
return bless {
val => $val,
}, $class;
}
}
use Types::Serialiser;
local $Types::Serialiser::true;
local $Types::Serialiser::false;
BEGIN {
$Types::Serialiser::true = MyBool->new(1);
$Types::Serialiser::false = MyBool->new(0);
}
use JSON::XS qw/decode_json/;
my $json = decode_json('{"true": true, "false": false}');
use Data::Dumper; warn Dumper($json);
注意としては, $Types::Serialiser::true
及び $Types::Serialiser::false
はreferenceを要求しているという点です.仮にここにreferenceではないscalar値なんかを突っ込むとなにが起きるかというと
use Types::Serialiser;
local $Types::Serialiser::true;
local $Types::Serialiser::false;
BEGIN {
$Types::Serialiser::true = 1;
$Types::Serialiser::false = 0;
}
use JSON::XS qw/decode_json/;
my $json = decode_json('{"true": true, "false": false}');
use Data::Dumper; warn Dumper($json);
$ perl json.pl
Segmentation fault: 11
SEGVします. JSON::XSはxsの処理の中身で Types::Serialiser::true
と Types::Serialiser::false
の中身を SV *
つまりreference相当に割り当てているためです.ここで Types::Serialiser::true|false
にscalar valueすなわち SV
を割り当てると当然SEGVしてしまいます.
このへん
https://metacpan.org/source/MLEHMANN/JSON-XS-3.03/XS.xs#L95
https://metacpan.org/source/MLEHMANN/JSON-XS-3.03/XS.xs#L1974
https://metacpan.org/source/MLEHMANN/JSON-XS-3.03/XS.xs#L136
<追記>
誤りの指摘を頂いたので修正しました.SvROKで事前チェックすれば良い気もするんですがどうなんでしょうね.
karupanerura [17:59]
リファレンスでもスカラ値でも `SV *` だから、ここでSvRVしているときにリファレンスじゃないとNULLが返ってくるのでSEGVしているというのが濃厚なきがする https://metacpan.org/source/MLEHMANN/JSON-XS-3.03/XS.xs#L139
[18:01]
リファレンスは `SvRV` というSVで、数値も `SvIV` というSVで差異はなくて、XSでSVを扱うときは基本的に `SV *` として扱うので、<追記ここまで>
きをつけましょう.