Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
TypeScriptで型レベルJSONパーサー
Search
kato-takeshi1
October 02, 2025
550
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
TypeScriptで型レベルJSONパーサー
JSON文字列のリテラル型を解析してTypeScriptに変換するやつ作りました
バグは多いですがおおかた動くくらいです
kato-takeshi1
October 02, 2025
Featured
See All Featured
StorybookのUI Testing Handbookを読んだ
zakiyama
31
7k
Music & Morning Musume
bryan
48
7.4k
Leveraging Curiosity to Care for An Aging Population
cassininazir
1
540
Visual Storytelling: How to be a Superhuman Communicator
reverentgeek
2
710
Facilitating Awesome Meetings
lara
57
7.2k
jQuery: Nuts, Bolts and Bling
dougneiner
66
8.6k
sira's awesome portfolio website redesign presentation
elsirapls
0
460
Optimizing for Happiness
mojombo
378
71k
Bridging the Design Gap: How Collaborative Modelling removes blockers to flow between stakeholders and teams @FastFlow conf
baasie
0
730
GraphQLとの向き合い方2022年版
quramy
50
15k
Being A Developer After 40
akosma
92
590k
Embracing the Ebb and Flow
colly
88
5.2k
Transcript
TypeScript で型レベル JSON パーサ Think! FrontEnd #8 合同会社 DMM.com 電子書籍開発部
25 新卒 加藤 豪
自己紹介 本名 加藤 豪 ニックネーム ERASER 大学 会津大学 領域 Web
FE 趣味 ゲーム、イラスト見る、VTuber 所属 二次元開発本部 電子書籍開発部 1 / 36
本日のお品書き 1. はじめに • 今回作ったもの • デモ 2. 概要 •
JSON パーサの構成 • Tokenizer とは • Parser とは 3. 実装 • Tokenizer の実装 • Parser の実装 • ところどころで実装テクニックを解説 4. 終わりに • やりきれなかったこと • 感想 2 / 36
はじめに
今回作ったもの 型レベル JSON パーサ 文字列リテラル型を JSON として解析する 型 type Input
= '{ "key": 123 }'; type Result = JsonParser<Input>; type Expected = { key: 123 }; 4 / 36
型…??? 🤔 様子がおかしいな…
はじめに ❌ JSON を解析する実行プログラム ⭕ JSON を解析する型 5 / 36
期待している方にお断り 1. 型なのでコンパイル時に JSON がわかってないと使えない • 動的に JSON を解析することはできない •
例えば API のレスポンスを解析することはできない 2. JSON の仕様に準拠しているわけではない • 適当です 6 / 36
概要
JSON パーサの構成 作成した JSON パーサの構成は大きく 2 つに分かれる • Tokenizer 字句解析
入力文字列をトークンに分割 • Parser 構文解析 トークン列型を解析して最終的な型に変換する 8 / 36
Tokenizer とは 文字列をトークンに分割する トークン 意味のある最小単位の文字列を表すもの • 例: ‣ 1234 ->
NumberToken<1234> ‣ "hello" -> StringToken<"hello"> ‣ { -> LeftBraceToken 9 / 36
Tokenizer とは 文字列をトークンに分割する '{ "key": 123 }' ↓ 分割 •
{ LeftBraceToken • “key” StringToken<"key"> • : ColonToken • 123 NumberToken<123> • } RightBraceToken 10 / 36
Parser とは トークン列型を解析して 最終的な型に変換する • { LeftBraceToken • “key” StringToken<"key">
• : ColonToken • 123 NumberToken<123> • } RightBraceToken ↓ 解析 { key: 123 } 11 / 36
実装
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
Tokenizer の実装(1/4) … 小さな Tokenizer まずは個別のトークンを抽出する 小さな Tokenizer を実装 これらはトークンを発見し以下を返す
• 抽出したトークン • 消費文字数 14 / 36
Tokenizer の実装(1/4) … 小さな Tokenizer 例えば true を抽出する TrueTokenizer は以下のように実装
type TrueTokenizer<S extends string> = S extends `true${string}` ? [TrueToken, 4] : never; 15 / 36
ちょっと解説 1 TS の型演算には if 文が存在しない extends を使うと条件分岐ができる 型1 extends
型2 ? 分岐1 : 分岐2 16 / 36
ちょっと解説 2 以下のようなものを template literal types と言います `true${string}` これは true
で始まる任意の文字列型にマッチします 17 / 36
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
Tokenizer の実装(3/4) … ループ処理 TokenizeOnce を使い文字列をループ処理して 文字列全体をトークン列に変換する TokenizerInner を実装 実装は複雑なので割愛します
コードは載せておくので興味があれば見てください 19 / 36
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
ちょっと解説 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
Tokenizer の実装(4/4) … 完成 型引数を隠蔽して完成! ! ! ! export type
Tokenizer<S extends string> = TokenizerInner<S>; 22 / 36
ふぅ…
Parser の実装(1/5) 特定の値を読み取る小さなパーサを実装 type ParseStringValue<Tokens extends Token[]> = Tokens extends
[StringToken<infer S>, ...infer Remain] ? [S, Remain] : never 23 / 36
Parser の実装(2/5) 小さなパーサを組み合わせてプリミティブな値(配列,オブジェクト以外) を読み取る ParsePrimitiveValue を実装 export type ParsePrimitiveValue<Tokens extends
Token[]> = | ParseStringValue<Tokens> | ParseNumberValue<Tokens> | ParseBooleanValue<Tokens> | ParseNullValue<Tokens> | EncounteredBad<Tokens> 24 / 36
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
Parser の実装(4/5) 配列をパースする ParseArray の実装 本実装は例によって割愛 export type ParseArray<Tokens extends
Token[]> = Tokens extends [SimpleToken.LeftSquareBracket, ...infer RemainTokens extends Token[]] ? ParseArrayElements<RemainTokens> : never 26 / 36
Parser の実装(5/5) ここまで作ったものを組み合わせて Parser の完成 export type Parser<T extends Token[]>
= | ParseObject<T> | ParseArray<T> | ParsePrimitiveValue<T>; 27 / 36
そして
そして Json パーサの完成 出来上がった Tokenizer と Parser を組み合わせて完成! ! !
export type JsonParser<S extends string = ""> = Parser< ArrayRemoveElement<Tokenizer<S>, SimpleToken.WhiteSpace> > extends [infer Result, ...infer _] ? Result : unknown 28 / 36
👍
デモ
工夫点 今回実装に際して工夫していた点 • テストをしっかりと用意した • 型引数のデフォルト値を活用し可読性を担保 29 / 36
工夫点 テストをしっかりと用意した 型レベルのテストはコードがコンパイル可能かどうかで判定可能 • コンパイルできる = 型があっている ‣ テスト成功 •
コンパイルできない = 型があっていない ‣ テスト失敗 30 / 36
工夫点 テストコードの例 function test_JsonParser() { { type Input = '{
"key": 123 }'; type Result = JsonParser<Input>; type Expected = { key: 123 }; // コンパイルエラーにならなければOK const _正常系_シンプルなオブジェクト = Equal<Result, Expected> = true; } } 31 / 36
工夫点 型引数のデフォルト値を活用し可読性を担保 型引数を元に以降の型引数のデフォルトを作成できる 変数宣言のように活用 → 名前がつくのでわかりやすく type ParseArrayElements< Tokens extends
Token[], ... ParseResult = ParsePrimitiveValue<Tokens> > = ... 32 / 36
終わりに
やりきれなかったこと • 小数点対応 • ネストされたオブジェクトや配列の対応 • TS の型システムの、無限ループになるかもエラーの解消 暇な時間があればここら辺も実装していきたい 💪
34 / 36
感想 • やはり型パズルは楽しい • 最後までできなかった部分はあれど、大分型筋がついた気がする • 今後勉強するものとして、型レベルの計算量などがあると気づいた ‣ 業務でもビルド時間の最適化などにも繋がるかも? ?
? – ほんとかなぁ? 35 / 36
リポジトリ 今回のコードは以下のリポジトリに公開しています 実装興味のある方はぜひ見てみてください URL: https://github.com/eraser5th/type-level-json-analyzer (スライド内のコードと異なる場合があります 🙇 36 / 36
ご清聴ありがとうございました 👋 良き型ライフを!