僕の作ったJSONパーザーのDynaJsonはとても厳格にできている。RFC 8259に準拠しているものはすべて受理するし、そうでないものは二つの例外を除きすべて受理しない。
受理する例外は、ケツカンマと02
や-02
のような0で始まる数字である。前者を受理するのは実用性のためであり、後者を受理するのはDynamicJsonとの互換性のためである。
JSONの文法はとても単純だが、不用意にパーザーを実装するとRFCに準拠したJSONを受理しなかったり、非準拠のものをエラーにできなかったりする。パーザーを実装する際の地雷ポイントはParsing JSON is a Minefieldによくまとまっている。
たとえば、null
のようにリテラルだけだと受理しないパーザーがある。[[][]]
のような空の配列だけの配列や、1e0
のように指数部が0の数字を受理しないものもある。{"a":1,"a":2}
のようにキーが一意でないものを受理しないパーザーは多い。
ほとんどのパーザーが、さまざまな非準拠のJSONを受理する。.2
や-.1
や+1
のような数字や、文字列にコントロール文字が直接入っているものや、[0]]
のように後ろのごみがあるものなど。
DynaJsonのために最初に書いたJSONパーザーは、思い込みと不注意からかなりの地雷を踏んでいた。最終的には準拠しているものはすべて受理し、準拠しないものは、入力の破損を検出するために二つの例外以外はエラーにするようにした。
Parsing JSON is a Minefieldの著者の作成したJSON Parsing Test Suiteを利用すると、このようなコーナーケースの動作確認ができる。.NETに対応した6つのJSONパーザーについて実行できるようにしたものをGitHubに置いた。その実行結果を示す。
左側がテストしたJSONのファイル名、真ん中が結果、右側がそのJSONである。ファイル名のうちy_
で始まるのは受理すべきもの、n_
は却下すべきもの、i_
はどちらでもよいもの。結果の色については左上の説明の通り。
.NET Core 3.0から入ったSystem.Text.Jsonはとても厳格であり、準拠のものをすべて受理し、非準拠のものをすべてエラーにする。ただし、オプションで指定すればケツカンマを受理する。この中でケツカンマを受理しないのはUtf8Jsonだけである。
パーザーごとに受理する非準拠のJSONに特徴がある。Json.NETはキーが文字列でないオブジェクトを受理し、Utf8Jsonは末尾のごみを許容する。DynamicJsonはオブジェクトが}
で閉じていないものを受理する。Jilは数値の指数部に寛容である。
一番下の赤になっている部分は、10万回ネストしたJSONをパーズした結果である。DynamicJsonは5秒で終わらずタイムアウトし、JilとUtf8Jsonはスタックオーバーフローでクラッシュしている。
Json.NETはデフォルトでメモリの許す限りネストが可能である。System.Text.Jsonはデフォルトで64までだがオプションで変更可能である。DynaJsonはデフォルトで512までだがオプションで変更できる。