$30 off During Our Annual Pro Sale. View Details »

TypeScriptで型レベルJSONパーサー

Avatar for kato-takeshi1 kato-takeshi1
October 02, 2025
340

 TypeScriptで型レベルJSONパーサー

JSON文字列のリテラル型を解析してTypeScriptに変換するやつ作りました
バグは多いですがおおかた動くくらいです

Avatar for kato-takeshi1

kato-takeshi1

October 02, 2025
Tweet

Transcript

  1. 自己紹介 本名 加藤 豪 ニックネーム ERASER 大学 会津大学 領域 Web

    FE 趣味 ゲーム、イラスト見る、VTuber 所属 二次元開発本部 電子書籍開発部 1 / 36
  2. 本日のお品書き 1. はじめに • 今回作ったもの • デモ 2. 概要 •

    JSON パーサの構成 • Tokenizer とは • Parser とは 3. 実装 • Tokenizer の実装 • Parser の実装 • ところどころで実装テクニックを解説 4. 終わりに • やりきれなかったこと • 感想 2 / 36
  3. 今回作ったもの 型レベル JSON パーサ 文字列リテラル型を JSON として解析する 型 type Input

    = '{ "key": 123 }'; type Result = JsonParser<Input>; type Expected = { key: 123 }; 4 / 36
  4. 期待している方にお断り 1. 型なのでコンパイル時に JSON がわかってないと使えない • 動的に JSON を解析することはできない •

    例えば API のレスポンスを解析することはできない 2. JSON の仕様に準拠しているわけではない • 適当です 6 / 36
  5. JSON パーサの構成 作成した JSON パーサの構成は大きく 2 つに分かれる • Tokenizer 字句解析

    入力文字列をトークンに分割 • Parser 構文解析 トークン列型を解析して最終的な型に変換する 8 / 36
  6. Tokenizer とは 文字列をトークンに分割する '{ "key": 123 }' ↓ 分割 •

    { LeftBraceToken • “key” StringToken<"key"> • : ColonToken • 123 NumberToken<123> • } RightBraceToken 10 / 36
  7. Parser とは トークン列型を解析して 最終的な型に変換する • { LeftBraceToken • “key” StringToken<"key">

    • : ColonToken • 123 NumberToken<123> • } RightBraceToken ↓ 解析 { key: 123 } 11 / 36
  8. Token の種類 トークンを以下のように定義 export type Token = | SimpleToken |

    StringToken | NumberToken; export type StringToken<...> = ... export type NumberToken<...> = ... enum SimpleToken { LeftBrace, // { RightBrace, // } LeftBracket, // [ RightBracket, // ] Colon, // : Comma, // , True, // true False, // false Null, // null End, // 入力の終端 Bad // トークナイズに失敗 } 13 / 36
  9. Tokenizer の実装(1/4) … 小さな Tokenizer 例えば true を抽出する TrueTokenizer は以下のように実装

    type TrueTokenizer<S extends string> = S extends `true${string}` ? [TrueToken, 4] : never; 15 / 36
  10. Tokenizer の実装(2/4) 小さな Tokenizer を組み合わせて トークンを 1 つ抽出する TokenizeOnce を実装

    type TokenizeOnce<S extends string> = | RightBraceTokenizer<S> | LeftBraceTokenizer<S> | ColonTokenizer<S> | TrueTokenizer<S> | FalseTokenizer<S> | WhiteSpaceTokenizer<S> | NullTokenizer<S> | LeftSquareBracketTokenizer<S> | RightSquareBracketTokenizer<S> | StringTokenizer<S> | CommaTokenizer<S> | NumberTokenizer<S> 18 / 36
  11. Tokenizer の実装(3/4) … ループ処理 実際のコード type TokenizerInner< S extends string,

    AccTokens extends Token[] = [], End = EndTokenizer<S> extends never ? false : true, Result extends [Token, number] = TokenizeOnce<S>, NextToken extends Token = Result[0], ReadedLength extends number = Result[1] > = End extends true ? [...AccTokens, SimpleToken.End] : Result extends never ? [...AccTokens, SimpleToken.Bad] : NextToken extends SimpleToken.Bad ? [...AccTokens, SimpleToken.Bad] : TokenizerInner<SkipString<S, ReadedLength>, [...AccTokens, NextToken]>; 20 / 36
  12. ちょっと解説 3 TS の型演算には for や while が存在しない ただし再帰関数よろしく型を再起的に呼び出せる type

    MakeTupleN< T extends unknown, N extends number, Acc extends unknown[] = [] > = Acc['length'] extends N ? Acc : MakeTupleN<T, N, [T, ...Acc]>; // 再帰 Listing 1: 長さ N のタプル型を作成 21 / 36
  13. Parser の実装(2/5) 小さなパーサを組み合わせてプリミティブな値(配列,オブジェクト以外) を読み取る ParsePrimitiveValue を実装 export type ParsePrimitiveValue<Tokens extends

    Token[]> = | ParseStringValue<Tokens> | ParseNumberValue<Tokens> | ParseBooleanValue<Tokens> | ParseNullValue<Tokens> | EncounteredBad<Tokens> 24 / 36
  14. Parser の実装(3/5) オブジェクトをパースする ParseObject の実装 本実装は長いので割愛 export type ParseObject<Tokens extends

    Token[]> = Tokens extends [SimpleToken.LeftBrace, SimpleToken.RightBrace, ...infer RemainTokens extends Token[]] ? [{}, RemainTokens] : Tokens extends [SimpleToken.LeftBrace, ...infer RemainTokens extends Token[]] ? ParseFields<RemainTokens> : never 25 / 36
  15. Parser の実装(4/5) 配列をパースする ParseArray の実装 本実装は例によって割愛 export type ParseArray<Tokens extends

    Token[]> = Tokens extends [SimpleToken.LeftSquareBracket, ...infer RemainTokens extends Token[]] ? ParseArrayElements<RemainTokens> : never 26 / 36
  16. そして Json パーサの完成 出来上がった Tokenizer と Parser を組み合わせて完成! ! !

    export type JsonParser<S extends string = ""> = Parser< ArrayRemoveElement<Tokenizer<S>, SimpleToken.WhiteSpace> > extends [infer Result, ...infer _] ? Result : unknown 28 / 36
  17. 工夫点 テストコードの例 function test_JsonParser() { { type Input = '{

    "key": 123 }'; type Result = JsonParser<Input>; type Expected = { key: 123 }; // コンパイルエラーにならなければOK const _正常系_シンプルなオブジェクト = Equal<Result, Expected> = true; } } 31 / 36