AI隆盛の昨今、データを読み込ませる軽量化フォーマットといえばこれまではJSONが主流となっています。ですが、将来JSONに代わるかもしれない新規格、toonというフォーマットが注目されてきています。
そして、このtoonに対する話題といえば、昨今の主流であるAIに絡めた記事がほぼ全てでした。しかし、jsonも元々はJSでデータ操作するための軽量化フォーマットとして開発された経緯を鑑み、このtoonもAI云々関係なしに、jsonのように汎用データフォーマットとして扱えるのではないか、そしてそれによって、なにか開発上のメリットを得られるのではないかという関心が沸きました。
ところが、このtoonフォーマットを外部ファイルからインポートしデータオブジェクトとして使用する方法がどこにも載っていなかったので、徹底的に方法を調査してみたものです。
toonフォーマットについて
開発チームは一番のメリットとして軽量化を挙げています。従来のJSONといえば、このような書き方で、ほぼJavaScriptのオブジェクトデータの形式と同じ(違いは末尾カンマ禁止ぐらい)です。
[
{"id":1,"name":"Seattle","area":"WA","act":false},
{"id":2,"name":"Portland","area":"OR","act":false},
{"id":3,"name":"Los_Angeles","area":"CA","act":false},
{"id":4,"name":"San_Francisco","area":"CA","act":false},
{"id":5,"name":"San_Diego","area":"CA","act":false}
]
ただデータフォーマットとしてはid、name、area、actというプロパティ部分が繰り返しになっていて冗長なのは一目瞭然ですし、角括弧や波括弧にカンマ、更にクォートなどの記述ルールも大いに混乱を招く要素となっています。
一方のtoonは以下のような記述方法となり、1行目にインデックス数とプロパティを記述してしまい、後の行は全部データに用います。
具体的には、冒頭の角括弧内にインデックス数を記載、その後{}の波括弧にカンマ区切りでプロパティを記載、デリミタの:を挟んで、2行目以下に、カラム数に呼応した具体的な値を記述していきます(ここの詳しい書き方は後で解説します)。
なお、拡張子はtoonとなります。
[5]{id,name,area,act}:
1,Seattle,WA,false
2,Portland,OR,false
3,Los_Angeles,CA,false
4,San_Francisco,CA,false
5,San_Diego,CA,false
もし、プロパティを付与したい場合は以下のようになります。
cities[5]{id,name,area,act}:
1,Seattle,WA,false
2,Portland,OR,false
3,Los_Angeles,CA,false
4,San_Francisco,CA,false
5,San_Diego,CA,false
このようにプロパティも数列程度なら可読性は確保されますし、行が簡潔で視認性、可読性も高いです。
また、これは文字列、これは数値、これは真偽値というようにクォートの有無で手を煩わせません。後述するように空値も、ただカンマを用意するだけです。
Reactでtoonファイルをインポートする
ここからが本番で、世の技術提供者が殆ど触れていなかった部分です。JSONフォーマットのように、既存プログラムに、jsonに代わってtoonフォーマットから任意データをインポートしてみました。
必要なライブラリをインストール
まずはtoonを使用するためにはデコーダが必要で、そのデコーダがtoon-format/toonというライブラリ(フォーマッター)に格納されています。
例はnpmですが、yarnなどでも大丈夫です。
# npm install @toon-format/toon
このライブラリからencodeというエンコーダとdecodeというデコーダを使用することができます。encodeはJSON形式をtoon化させ、逆にdecodeはtoon形式をオブジェクトにデコードする役割を持ちます。
import { encode,decode } from '@toon-format/toon'; //ライブラリのインポート
import CitiesToon from '../json/city2.toon'; //toonファイルを読み込ませる
const cities = decode(CitiesToon); //toonをデコードする
console.log(cities)
データ取得の道のり
ここからが苦難の連続でした。まず、toonフォーマットを適用したファイルを読み込ませようとしても、次のようなメッセージが表示されます。
Uncaught SyntaxError: Unexpected token '{' (at
懸念通り、toonを全く読み込めていないようです。バージョンが原因なのか何度もライブラリのインストール、アンインストールを繰り返しても解決せず。根本的なこの原因を調査してみるとtoonという規格がまだ一般化していないようで、以下の記述でtoonファイルは文字列であることをプログラムに教えないといけないようです。
その書き方が以下の記述です。
import CitiesToon from '../json/city2.toon?raw'; //文字列であることを教える
?rawはこれからインポートするファイルは「生」(ただのテキスト)であるということをプログラムに教えるというものです(これはViteだけの記述形式なので注意。npmの場合は別の記述形式が必要です)。
データの記述方法
これでtoonファイルは読み込めました。ところが、今度はtoonファイルのデータそのものが読み込めないというエラーが発生しました。
@toon-format_toon.js?v=a37abb4c:326 Uncaught RangeError: Expected 1 tabular rows, but got 0
タブがどうとか言っている(実際は、tabular rowsとは行のデータ形式のことで、タブとはあまり関係ありませんでした)ので、2行目からのデータはタブが必要なのかと思いタブインデントを空けてみました。
Uncaught SyntaxError: Line 2: Tabs are not allowed in indentation in strict mode<.strong>
どうもタブインデントではないようです(ちなみにstrict modeを許容する方法をプロパティに付与することもできますが、それを実施するとエラーは表示されなくなる代わりに、肝心のデータ自体を読み込もうとしません)。
次の手段として一度jsonを読み込ませ、encodeでtoonフォーマット化し、それをブラウザのコンソールに出力してエディタに複写したものを、toonファイルとして読み込ませてみました。どうもデータ部分はスペースを空ける必要があったみたいです。
ところが……
Uncaught SyntaxError: Line 2: Indentation must be exact multiple of 2, but found 1 spaces (at
まだエラーが出ます。ですが、ここでエラーメッセージを注意深く読んでみると「インデントはちょうど2つ分空けないといけない、1つのスペースしか空けてない」と英語で明確に注意しています。
つまり、以下が正しい書き方です(ブラウザコンソールだと複数スペースは無視されるので、ここが盲点でした)。
データ部分は半角スペースを2つ空ける
[1]{id,name,area,act}:
1,Seattle,WA,false #スペースは2つあけること!
これでjsonと同じようにインポートできます。入れ子にしたい場合は2スペースずつずらしていくことになります。
※エディタのEOFも確認してみたのですが、全く干渉しないようです。
メソッドから読み込ませる
メソッドから読み込ませる場合は、fetchを用い取得したファイルをテキスト化するだけで対応できます。念のため、asyncとawaitを使用すると確実です。
例はカスタムフック化し、コンポーネントでuseToonとするだけでJSONのように、簡潔にデータを取得できるようにしています。ちなみにimportと違い、ファイルのパスはpublicフォルダ直下に置くと、設定は不要です。
import { decode } from "@toon-format/toon";
import { useEffect } from "react";
async function get_toon(){
try{
const res = await fetch(`./toon/data.toon`); //toonファイルのインポート(./public/toon直下)
return await res.text(); //テキスト化する
} catch (error){
throw error;
}
}
function useToon(){
useEffect(()=>{
const datum = get_toon(); //toonデータの取得
const words = decode(datum); //デコード
return words;
},[])
}
export default useToon;
toonフォーマットの利便性
自分としては開発者が挙げていた読み込みの軽量化、高速化というより、データ追記作業において格段の利便性を感じました(特に単階層データの場合です。他の方も指摘している通り、入れ子構造となった場合はjsonの方が格段に視認性が高いですが、昨今ではあまり入れ子構造にすることを推奨していません)。
※データ原本は表計算ソフトで編集、エディタでの作成作業はマクロ化やCopilotなどに命令させるだけで簡単に作業できます。
利点1:IDのリナンバリング
たとえば、既存のデータに新たに値を挿入したい場合です。jsonの場合IDのリナンバリングに際しては、他のプロパティも逐一書き直さないといけなくなり、非常に不便です。ですが、toonだと表計算ソフトでオートフィルしてリナンバリングするだけです。
例1:データの3列目にSacramentoを追記しリナンバリング
[6]{id,name,area,act}:
1,Seattle,WA,false
2,Portland,OR,false
3,Sacramento,CA,false #新たに追記したデータ
4,Los_Angeles,CA,false
5,San_Francisco,CA,false
6,San_Diego,CA,false
利点2:プロパティの追加
また、プロパティを追加、挿入したい場合もすぐです。表計算ソフトに列を追記するだけです。
例2:popsという人口用の列を追加
[6]{id,name,area,act,pops}:
1,Seattle,WA,false,737015
2,Portland,OR,false,652503
3,Sacramento,CA,false,524943
4,Los_Angeles,CA,false,3898747
5,San_Francisco,CA,false,873965
6,San_Diego,CA,false,1386932
利点3:空値、欠損値の対応
また空値、欠損値に関しても列相当分のカンマを付与しておくだけ(nullなどは不要)です。
例3:mlb,nba,nflというメジャースポーツチーム名を追加(ない場合は空値)
[6]{id,name,area,act,mlb,nba,nfl}:
1,Seattle,WA,false,Mariners,,Seahawks #シアトルにNBAはない
2,Portland,OR,false,,Trail Blazers, #ポートランドにMLB、NFLはない
3,Sacramento,CA,false,,Kings, #サクラメントにMLB、NFLはない
4,Los_Angeles,CA,false,Dodgers,Lakers Clippers,Lambs Chargers
5,San_Francisco,CA,false,Giants,Warriers,49ers
6,San_Diego,CA,false,Padres,, #サンディエゴにNBA、NFLはない
尚、今回はReactだけで試していますが、おそらくVue、Angular、Svelteなどでも普通にインポート可能のはずです。
その他の特徴
toonのデータはあくまでインデントが連続したデータ行のみをデータとして認識するようです。したがって、一度インデントが解除されると、以下の行はインデントの有無に関係なくデータ対象外となります。
[6]{id,name,area,act,pops}:
1,Seattle,WA,false,737015
2,Portland,OR,false,652503
3,Sacramento,CA,false,524943 #ここでインデントが解除されているので、読み込むのは2行のみとなる
4,Los_Angeles,CA,false,3898747 #ここはもう読み込まない
5,San_Francisco,CA,false,873965 #ここはもう読み込まない
6,San_Diego,CA,false,1386932 #ここはもう読み込まない
AIへのファイルインポート対策
toonに格納したデータに対し、各都市の緯度と経度を探索してもらおうとしてtoonファイルをインポートすると、Copilot、Geminiのいずれからも「この規格は対象外です」と怒られました。そんな場合はencodeを使って、toonファイルをjsonファイルにエンコードすることもできます。
import { encode } from "@toon-format/toon"; //toonライブラリからencodeを呼び出す
const file_json = encode('hoge.toon'); //toonからjsonにエンコード可能