Programming Field

[TypeScript] TS 4.1 の機能で型のみでJSONパーサーを書いてみたが…

「TS 4.1 の機能で雑に型の足し算・引き算を作る」と同様に、2020年11月リリースの TypeScript 4.1 にある「テンプレートリテラル型」(Template literal types)を用いて、自分なりに型だけでJSONパーサーを書いてみましたが、その感想をここに記してみます。

はじめに

JSONは JSON.org に仕様が公開されていますが、構文としては比較的シンプルなものになっています。

この記事を書いた時点で、既に 「jamiebuilds/json-parser-in-typescript-very-bad-idea-please-dont-use」(GitHub) のように型だけでJSONパーサーを書かれているものが存在していますが、自分でもどこまでできるか書いてみたという感じになります。

※ なお、上記で紹介したリポジトリでは数値(number)の解析には対応できていませんが、以下のソースコードでは一応対応しています。

ソースコード

※ Playgroundへのリンク → TS Playground (4.1.0-dev.20201015)
※ 以下に gist.github.com にアップロードしたソースコードを埋め込んでいます。表示されない場合はこちら → https://gist.github.com/jet2jet/e51069feb975da2f19eb66d8aee50464

メイン

サンプル1 (JSON.parse)

サンプル2 (JSON Schema を解釈)

結果など

※ いずれも 4.1.0-dev.20201015 時点での確認です。

  • ParseJSON ではJSONの型そのままではなく、一種の構文木(AST)を生成しています。
    • それを実際の型に変換するにはサンプル1の TypeFromJSONStructure を使います。
    • また、サンプル2の TypeFieldToType のように、構文木に対して具体的な解釈を入れることもできます。(サンプル2は簡易的なJSON Schema解析を行っています。)
    • 基本的にはJSONの仕様に則って記述していますが、再帰制限(条件付き型などによるネストの上限)に達しないように一部パース処理を結合している箇所があります。(例: 「members」と「member」の解析を統合)
  • 制限などについて:
    • 数値は明確な終了記号がないために1文字ずつ解釈しなければならないため、再帰回数が増えてしまい、それほど長くない文字列リテラルであっても再帰制限に抵触して TS2589 エラーとなってしまいます。
    • また、整形するためにスペース文字や改行文字を多く入れた場合も、やはり原則として1文字ずつの解釈が必要になるため、再帰制限に引っかかりやすくなります。これについては、スペースを最初から削っておくことで多少粘ることは可能です。また、前述のソースコードでは「ある程度の文字数」までは一括で削るような対応を入れています(が、限界はあります)。

「制限などについて」に書いたように再帰制限に引っかかりやすいため、小さめの文字列リテラルであれば期待通りの結果が得られそうですが、現実的な実用性はあまりないと感じます。このことから、テンプレートリテラル型は(元のPull Requestにあるような)部分的な文字列の解析には適している一方、大きな文字列を解析するには現時点では適していないと思われます(再帰制限が緩和されたとしてもコンパイラーに大きな負荷がかかりそうに感じます)。

[追記 2021/09/20] TypeScript 4.5 にて型を再帰させる際の記法によって再帰の回数の制限が緩和されるという対応が入るため(refs. TypeScript PR #45711)、工夫次第では多少大きなJSONもパース可能になる可能性があります。(ただし本サイトでは改良版を検討・掲載する予定はありません。)

その他・ライセンス

  • ソースコード冒頭にリンクしているライセンスにもあるように、本ソースコードのライセンスは gist jet2jet/LICENSE に従います。(2020年10月時点では0条項BSDライセンスとしており、現時点で変更の予定はありません。)
  • ソースコードに対して誤り等がありましたら、gist 上のコメント欄にてコメントをいただければと思います。(「当サイトについて」からリンクしているメールフォームからでも構いません。)

更新履歴

  • 2021/09/20 - TS4.5に関する補足を追加
  • 2020/10/15 - 作成