2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LLM向け新フォーマット「TOON」はCursorのChat機能でもトークン節約になるのか検証してみた

2
Posted at

はじめに

はじめまして!@HirokiLucky24です!この記事はいろいろやってみる Advent Calendar 2025の 1 日目の記事です。今回はひとりアドカレを初めてやってます!自分の興味があることをとにかくいろいろやってみる記事を書いています
今回は最近話題となったTOONについて興味を持ったため、調べてみました!

LLM向け新フォーマット「TOON」 ─ JSONよりトークン効率の良い構造化表現

LLM 向けのプロンプトを作っていると、JSON がそのままだとトークン数が重い・行数が多く読みにくい・フォーマットを守らせるのが大変、と感じたことはないでしょうか。

この記事では、そうした問題を解決するために設計された TOON(Token-Oriented Object Notation) という新フォーマットを、公式リポジトリ toon-format/toon をベースに整理して紹介し、また後半ではCursorなどのChat機能でもトークン効率が変わるのかを調べてみる記事になっています

TOONとは?

TOON(Token-Oriented Object Notation) は、一言で言うと:

  • インデントと表形式で短く記載できる
  • AIが理解しやすい
  • トークンコストを削減できる

といった特徴を持つ、LLMプロンプト用のJSONデータモデルをコンパクトかつ人間が読めるフォーマット で、LLMプロンプト向けの JSON 代替フォーマットになるかもといわれており、特に「大量の配列データ(リスト・テーブル)を LLM に渡したいときに JSON よりも効率が良いフォーマット」です

JSONとTOONの違いを具体例で見る

まずは、公式 README に掲載されている例から、JSON と TOON の違いを見てみます。

JSON での表現

{
  "repositories": [
    {
      "id": 28457823,
      "name": "freeCodeCamp",
      "repo": "freeCodeCamp/freeCodeCamp",
      "description": "freeCodeCamp.org's open-source codebase and curriculum. Learn math, programming,…",
      "createdAt": "2014-12-24T17:49:19Z",
      "updatedAt": "2025-10-28T11:58:08Z",
      "pushedAt": "2025-10-28T10:17:16Z",
      "stars": 430886,
      "watchers": 8583,
      "forks": 42146,
      "defaultBranch": "main"
    },
    {
      "id": 132750724,
      "name": "build-your-own-x",
      "repo": "codecrafters-io/build-your-own-x",
      "description": "Master programming by recreating your favorite technologies from scratch.",
      "createdAt": "2018-05-09T12:03:18Z",
      "updatedAt": "2025-10-28T12:37:11Z",
      "pushedAt": "2025-10-10T18:45:01Z",
      "stars": 430877,
      "watchers": 6332,
      "forks": 40453,
      "defaultBranch": "master"
    }
  ]
}

この JSON では、各要素ごとに "id", "name" などの キー名が繰り返し 出てきます。
人間にも機械にも分かりやすい一方で、トークン数の観点では無駄が多い状態です。

TOON での表現

同じデータを TOON で書くと、次のように表現できます。

repositories[2]{id,name,repo,description,createdAt,updatedAt,pushedAt,stars,watchers,forks,defaultBranch}:
  28457823,freeCodeCamp,freeCodeCamp/freeCodeCamp,"freeCodeCamp.org's open-source codebase and curriculum. Learn math, programming,…","2014-12-24T17:49:19Z","2025-10-28T11:58:08Z","2025-10-28T10:17:16Z",430886,8583,42146,main
  132750724,build-your-own-x,codecrafters-io/build-your-own-x,Master programming by recreating your favorite technologies from scratch.,"2018-05-09T12:03:18Z","2025-10-28T12:37:11Z","2025-10-10T18:45:01Z",430877,6332,40453,master

ヘッダ行 でスキーマを宣言し、その下に 値だけを行単位で並べる のが TOON の基本的なスタイルです。

  • repositories … フィールド名(JSON のキー名に相当)
  • [2] … 要素数(この例では 2 件)
  • {id,name,repo,...,defaultBranch} … 各行が持つ列(カラム)=スキーマ
  • その下の行 … カンマ区切りで値を並べた「1 レコード」

このように、不要な記号が少なく、トークン数も大きく削減されます。
また、人間にとっても「表形式」のように見えるので、長いリストでも目で追いやすいのが特徴です。

TOONの設計思想と特徴

公式 README やドキュメント(toon-format/toon)をもとに、TOON の設計思想を整理すると次のようになります。

  • トークン効率重視(Token-Oriented)
    • JSON では重複していたキー名をヘッダ行に集約し、データ行では値だけを書く
    • デリミタ(カンマ / タブなど)も、LLM のトークナイザにとって効率的になるよう設計
  • スキーマを明示できる
    • {id,name,repo,...} の部分で「このリストの各要素がどんなカラムを持つか」を宣言できる
    • LLM に「このスキーマで出力して」と指示しやすい
  • 人間にとっても読みやすい
    • 大量の配列データが「表形式」になるため、ログや結果の確認がしやすい
  • LLMに優しいプロンプトフォーマット
    • フォーマットの仕様を長々と説明しなくても、サンプルを数個見せればパターンを学習してくれる 前提で設計されている

導入方法:CLI と TypeScript ライブラリ

TOON には、JSON ↔ TOON の相互変換やトークン数の比較ができる CLI と TypeScript ライブラリが用意されています。

CLI(npxでお手軽に試す)

Node.js が入っていれば、インストールなしで npx から試せます。

# JSON → TOON
npx @toon-format/cli input.json -o output.toon

# 標準入力からパイプでTOONへ
echo '{"name": "Ada", "role": "dev"}' | npx @toon-format/cli

# TOON → JSON も自動判別で可能
npx @toon-format/cli data.toon -o output.json

オプションとして --stats を付けると、どれくらいトークンを節約できたかの統計も出力してくれます。

TypeScript ライブラリ

アプリケーション側から TOON を扱いたい場合は、TypeScript ライブラリを利用します。

# npm
npm install @toon-format/toon

# pnpm
pnpm add @toon-format/toon

# yarn
yarn add @toon-format/toon

使用例は次の通りです。

import { encode } from '@toon-format/toon'

const data = {
  users: [
    { id: 1, name: 'Alice', role: 'admin' },
    { id: 2, name: 'Bob', role: 'user' }
  ]
}

console.log(encode(data))
// users[2]{id,name,role}:
//   1,Alice,admin
//   2,Bob,user

既存コードでは内部で JSON を扱い、LLM に渡すときだけ TOON に変換する といった構成も取りやすそうです。

また、公式リポジトリでは TypeScript 以外の言語向け実装も順次整備されています。
公式実装として現時点では .NET / Dart / Go / Python / Rust 版が開発中であり、コミュニティ実装としても C++ / Clojure / Crystal / Elixir / Gleam / Go / Java / Scala / Lua(Neovim) / OCaml / PHP / Laravel / R / Ruby / Swift / Kotlin など、多くの言語向けライブラリが公開されています。 (toon-format/toon の README

Cursorでのトークン数実験(ざっくり比較する方法)

さて、TOONについてだいたい理解できました。LLM用ということもあり、LLMで使う分には可視性などを含め良い結果になりそうです。しかし、Chat機能で使う分にはどうなるでしょうか?JSONで返ってくるものがあるとして、Chat機能を使って調べるときにTOONに変換してから聞いたほうがトークンの節約になるのでしょうか?
LLMでの結果は公式でまとめられているので、Chat機能で使ったときという視点から実際に JSON と TOON、 CSV でトークン数を比べてみます
ただし、Cursor の管理画面に表示される「Tokens(Input)」をそのまま比較しても、厳密な意味では JSON と TOON、CSV の差だけを見ているわけではありません。

1 回のリクエストの Input tokens には、次のようなものがすべて含まれます。

  • システムメッセージ(「あなたはアシスタントです」など)
  • 開発者メッセージ(ツール定義やルール)
  • 過去の会話履歴
  • 自分が貼った JSON / TOON 本文

このため、イベントログやダッシュボードに出るトークン数は「合計値」であり、JSON と TOON、CSV の差分だけをきれいに切り出すことはできません。そのためざっくりとコスト感を掴み、「自分が書いた部分がどれくらい節約できているか」を見てみたいと思います

また以下の順番で検証します

  1. 新しいチャットを作る
    履歴のない、まっさらな状態から始める。
  2. JSON 版だけTOON 版だけ、CSV 版を貼る
    モデルへの指示文(プロンプトの文章部分)は両方まったく同じにする。
  3. それぞれ 1 回だけメッセージを送る
    以降の会話は行わず、「1 リクエスト = 1 メッセージ」の状態にしておく。
  4. 各イベントの Input tokens を見る

この方法でもシステムメッセージや内部の開発者メッセージは含まれていますが、チャット間でそこは共通なので、差分として見える部分はほぼ「自分が書いた JSON と TOON と CSV の違い」 になります。

実験用サンプルデータ(JSON / TOON / CSV)

この記事で説明してきた内容を踏まえて、同じデータを JSON・TOON・CSV の 3 形式で用意したシンプルなサンプルを使います。

  • TOON
users[2]{id,name,role}:
  1,Alice,admin
  2,Bob,user
  • JSON
{
  "users": [
    { "id": 1, "name": "Alice", "role": "admin" },
    { "id": 2, "name": "Bob",  "role": "user"  }
  ]
}
  • CSV
id,name,role
1,Alice,admin
2,Bob,user

この 3 パターンを下記質問文と一緒にモデルに渡します

What is Alice's role?
  • 質問文のトークンは共通部分
  • データ表現(JSON / TOON / CSV)のトークンだけが違う部分

として比較しやすくなります。

実際に Cursor でこのサンプルと質問文を使って計測したところ、

  • TOON: Input tokens ≒ 135
  • JSON: Input tokens ≒ 163
  • CSV: Input tokens ≒ 126

という結果になり、同じ意味のデータでもフォーマットによって 1〜3 割程度トークン数が変わり得る ことが確認できました。
一方で、イベント全体の Input tokens はシステムメッセージやツール定義なども含むため、合計が 7,000 トークン以上になることもあり、本文が小さくても数値だけ見ると大きく見える 点には注意が必要です。

さらに、OpenAI の公式トークナイザ(https://platform.openai.com/tokenizer)で、上記のサンプルデータと質問文だけを個別に入力してみると、

  • TOON: 36 tokens
  • JSON: 64 tokens
  • CSV: 27 tokens

という結果になりました。
こちらは「モデルに渡す生の文字列部分だけ」を見た値なので、フォーマットそのもののトークン効率差を、よりダイレクトに確認できる指標 として使えます。


もう少し大きい構造体での比較用サンプル

上のユーザ一覧はかなり小さい例なので、もう少し現実に近い「商品一覧」的な構造も 3 形式で用意しておきます。
カテゴリ・タグ・在庫情報などを含めた、先ほどとは比較的大きいデータです。

  • TOON
products[4]{id,name,category,price,discount_rate,in_stock,stock_count,tags,metadata}:
  1001,"Wireless Mouse","Peripheral",2980,0.10,true,120,"wireless;mouse;bluetooth","color=black;dpi=1600;brand=FooTech"
  1002,"Mechanical Keyboard","Peripheral",12800,0.15,true,45,"keyboard;mechanical;rgb","color=white;switch=brown;brand=FooTech"
  1003,"27-inch 4K Monitor","Display",49800,0.20,false,0,"monitor;4k;ips","color=black;panel=IPS;brand=BarDisplay"
  1004,"USB-C Hub 7-in-1","Accessory",5980,0.00,true,300,"usb-c;hub;dongle","color=space-gray;ports=7;brand=BazGadget"
Json
{
  "products": [
    {
      "id": 1001,
      "name": "Wireless Mouse",
      "category": "Peripheral",
      "price": 2980,
      "discountRate": 0.10,
      "inStock": true,
      "stock": {
        "count": 120
      },
      "tags": ["wireless", "mouse", "bluetooth"],
      "metadata": {
        "color": "black",
        "dpi": 1600,
        "brand": "FooTech"
      }
    },
    {
      "id": 1002,
      "name": "Mechanical Keyboard",
      "category": "Peripheral",
      "price": 12800,
      "discountRate": 0.15,
      "inStock": true,
      "stock": {
        "count": 45
      },
      "tags": ["keyboard", "mechanical", "rgb"],
      "metadata": {
        "color": "white",
        "switch": "brown",
        "brand": "FooTech"
      }
    },
    {
      "id": 1003,
      "name": "27-inch 4K Monitor",
      "category": "Display",
      "price": 49800,
      "discountRate": 0.20,
      "inStock": false,
      "stock": {
        "count": 0
      },
      "tags": ["monitor", "4k", "ips"],
      "metadata": {
        "color": "black",
        "panel": "IPS",
        "brand": "BarDisplay"
      }
    },
    {
      "id": 1004,
      "name": "USB-C Hub 7-in-1",
      "category": "Accessory",
      "price": 5980,
      "discountRate": 0.00,
      "inStock": true,
      "stock": {
        "count": 300
      },
      "tags": ["usb-c", "hub", "dongle"],
      "metadata": {
        "color": "space-gray",
        "ports": 7,
        "brand": "BazGadget"
      }
    }
  ]
}
CSV
id,name,category,price,discount_rate,in_stock,stock_count,tags,metadata
1001,"Wireless Mouse","Peripheral",2980,0.10,true,120,"wireless;mouse;bluetooth","color=black;dpi=1600;brand=FooTech"
1002,"Mechanical Keyboard","Peripheral",12800,0.15,true,45,"keyboard;mechanical;rgb","color=white;switch=brown;brand=FooTech"
1003,"27-inch 4K Monitor","Display",49800,0.20,false,0,"monitor;4k;ips","color=black;panel=IPS;brand=BarDisplay"
1004,"USB-C Hub 7-in-1","Accessory",5980,0.00,true,300,"usb-c;hub;dongle","color=space-gray;ports=7;brand=BazGadget"

あわせて、モデルには次のような「少し計算が必要な質問」を一緒に投げています

## 4つすべての商品を、それぞれ割引後の価格で1個ずつ購入するとき、合計金額はいくらになりますか?
If you buy one unit of each of the four products at their discounted prices, what is the total cost?

実際に Cursor でこの商品一覧サンプルと上記の質問文を使って計測したところ、Input tokens のおおよその値は次のようになりました。

  • TOON: Input tokens ≒ 325
  • JSON: Input tokens ≒ 590
  • CSV: Input tokens ≒ 312

同じ条件の文字列を OpenAI の公式トークナイザ(https://platform.openai.com/tokenizer)に入力した場合は、

  • TOON: 226 tokens
  • JSON: 487 tokens
  • CSV: 213 tokens

といった結果になりました。
小さなサンプルと同様に、構造が大きくなっても TOON は JSON よりトークン数を抑えつつ、CSV よりも構造的な表現を維持できている ことが分かります。

一方で、Cursor のイベント全体の Total tokens で見ると、最初の小さい構造体の実験ではどの形式でもおおよそ 1.2 万トークン前後、商品一覧のような大きめの構造体では 1.3 万トークン前後 でした。つまり、ほぼ全てがInput、Output以外が占めています。そのため JSON / TOON / CSV の違いによる増減は数百トークン程度なので埋もれてしまい、顕著な差としては現れませんでした

またInput量を増やせば差が出るのではないかと考え、同じ構造を持つ 1 万行規模の TOON10 万行超の TOON を比較したところ、行数の増加に伴い Input tokens は多少増加したものの、イベント全体の Total tokens に占める割合は依然として小さく、構造体サイズを増やしても Token 全体から見ると Input 部分の増減は誤差レベルにとどまる という結果でした。

つまり、少なくとも Cursor のチャット機能のイベントログの数字だけを見る限りは、「チャット画面に貼る前にわざわざ TOON へ変換しても、総トークン数の観点ではほとんど効果を実感できない」 というのが実測ベースの結論です。

まとめ

  • 結局 CSV で良いのではなど賛否両論がありますが、大量の配列データでもトークン数を削減しつつ読みやすさも維持 できるというバランスのとれたフォーマットではあるので今後どのように使われるのかきになりますね。
  • Chat機能のトークン数では、結局 Input、Output 以外でトークンを使っていたので、節約できているかという視点では差は見れませんでした。しかし、Input視点では CSV に似ている部分もあり、JSONに比べて小さいことがわかりました。

TOON 自体はまだ新しいフォーマットですが、「LLM に大量の構造化データを渡す」 というユースケースではかなり強力な選択肢になり得ます。
興味があれば、まずは手元の JSON を TOON に変換して、トークン数の削減効果やプロンプトの読みやすさ を試してみると面白いと思います。

参考: toon-format/toon

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?