その手の平は尻もつかめるさ

ギジュツ的な事をメーンで書く予定です

JSON::XSでデシリアライズする時にtrueとfalseの扱いを変更する

Perl5の話題です.
JSON::XSを用いて,JSON StringをPerlのHashRefにデシリアライズする時にtrueとfalseの扱いを変えたいという話です.

デフォルト状態でJSON::XSを用いてデシリアライズすると, trueTypes::Serialiser::true すなわち JSON::PP::Boolean の真値として, falseTypes::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::trueTypes::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 *` として扱うので、<追記ここまで>

きをつけましょう.