前回の
JSONエンコードをわかりやすく変更しやすくする
ISUCON11のJSONエンコードでは、Cpanel::JSON::XS
のバージョン4.JSON_
を、JSON_
を、JSON_
を指定し、Cpanel::JSON::XS::Type
で型を明示している例です。
use Cpanel::JSON::XS;
use Cpanel::JSON::XS::Type;
# どうエンコードしたいか第二引数に指定できる
encode_json({
name => "foo",
age => 10,
flag => 1
}, {
name => JSON_TYPE_STRING,
age => JSON_TYPE_INT,
flag => JSON_TYPE_BOOL,
});
# => {"name":"foo","age":10,"flag":true}
以降では、JSON::Types
との違いに触れ、Cpanel::JSON::XS::Type
の利点を説明します。
JSONエンコードの罠たち
PerlのJSONエンコードでは、Cpanel::JSON::XS::Type
やJSON::Types
といったモジュールの開発動機になります。
数値が、意図せず文字列になる罠
複数のプログラミング言語を使うとき、
具体的な例で説明すると、12
を1つ持つ配列をJSONエンコードすれば、[12]
となりますが、JSON::XS
のバージョン4.["12"]
となり12
は文字列扱いされます。
use JSON::XS;
my $num = 12; …(1)
warn "[DEBUG] num: $num \n"; …(2)
encode_json([$num]); # => ["12"]
この原因は、IV
、$num
が$num
は文字列PV
、IV
)PVIV
となります。ここで問題なのが、JSON::XS
では、["12"]
となります。一方、Cpanel::JSON::XS
やJSON::PP
のバージョン2.[12]
となります。いずれにしろ、
Perlで偽となる値が、JSONで偽とならない罠
1==0
や!!0
をJSONエンコードした場合、false
となることを期待しますが、false
とエンコードしたい場合、\0
JSON::XS::false
などを利用する必要があります。
encode_json([1 == 0]); # [""]
encode_json([!!0]); # [""]
encode_json([\0]); # [false]
encode_json([JSON::XS::false]); # [false]
現在開発版であるPerl 5.
罠をシンプルに解決するJSON::Types
過去のISUCONで利用されていたJSON::Types
は、number
関数を、bool
関数を挟み、
use JSON::Types;
my $num = 12;
warn "[DEBUG] num: $num \n";
encode_json([number $num]); # => [12]
encode_json([bool !!0]); # => [false]
中身はシンプルで、number $num
であれば$num + 0
と等価で、bool !!1
であれば!!1 ? \1 : \0
と等価で、
JSON::Typesの弱点
JSON::Types
は簡潔で使いやすいユーティリティですが、
1つ目は、JSON::Types
のnumber
関数で数値を期待する処理をしたあとに、
2つ目は、number $row->{id}
したあとに、$cache->{$row->{id}}
とキャッシュを取り出すコードです。
my $row = { id => 123 };
my $cache = { 123 => "hello" };
$row->{id} = number $row->{id};
$row->{value} = $cache->{$row->{id}};
encode_json($row);
# {"id":"123","value":"hello"}
これのJSONエンコードの結果は、id
が意図せず"123"
と文字列になります。原因は、$row->{id}
がIV
からPVIV
になったためです。このコードであれば、$cache->{$row->{id}}
を疑えるかもしれませんが、
3つ目は、$detail->{id}
をnumber
で変換するために二重ループしています。また、@json_
の構造、
use JSON::XS;
use JSON::Types;
my @json_items;
for my $item ($items->@*) {
my @json_details;
for my $detail ($item->{details}->@*) {
push @json_details => {
id => number $detail->{id},
timestamp => number $detail->{timestamp},
}
}
push @json_items => {
name => string $item->{name},
details => \@json_details,
}
}
encode_json(\@json_items);
Cpanel::JSON::XS::TypeでJSONエンコードの罠とおさらば
ISUCON11のPerl実装では、Cpanel::JSON::XS::Type
を利用しました。次のコードは、
# 期待するJSONの構造を宣言
use constant ItemDetail => {
id => JSON_TYPE_INT,
timestamp => JSON_TYPE_INT,
};
# Itemは、ItemDetailのリストをネストしている
use constant Item => {
name => JSON_TYPE_STRING,
details => json_type_arrayof(ItemDetail),
};
# 取得してきたデータ($items)を
# 型と一緒に渡してJSONエンコードする
encode_json(
$items,
json_type_arrayof(Item)
);
このコードは、JSON::Types
の弱点を克服します。
まず、encode_
する際、ItemDetail
やItem
のような部品ごとに構造を書けます。
さらに、
encode_json({ boo => 1.0 }, { foo => JSON_TYPE_FLOAT });
# => ERROR: no type was specified for hash key 'boo'
まとめ
本稿では、
次に、
最後に、Cpanel::JSON::XS::Type
で期待するJSON構造を明示することで、
開発の事情でPerlのバージョンを変更することが難しかったり、
本誌最新号をチェック!
WEB+DB PRESS Vol.130
2022年8月24日発売
B5判/168ページ
定価1,628円
(本体1,480円+税10%)
ISBN978-4-297-13000-8
- 特集1
イミュータブルデータモデルで始める
実践データモデリング
業務の複雑さをシンプルに表現! - 特集2
いまはじめるFlutter
iOS/Android両対応アプリを開発してみよう - 特集3
作って学ぶWeb3
ブロックチェーン、スマートコントラクト、NFT