「id」的なものが数値だとして、表現を切り詰めたくて。

この話に呼応する話。

以前この話を読んだ人は、ごめんよ、すげー根本的な間違いを書いてた。追記しといたが、こんな JSON は不正ね:

javascript としては正当でも JSON としては不正
1 {a: 1}

本題。

その引用先で考えていたのは「javascript としてデータ読み込んだ際のメモリ使用量をなるべく減らしたいのさ」てことだった。それゆえに「キー名の長さを切り詰める」ということを考えたわけである。これはまぁ当然効果は相当出るわけだが、例にしてた「MAL の ID」、これってさ、全部数値なわけよ。となるとね、JSON としての意味ではサイズ削減出来るわけ:

こうではなく
 1 {
 2   "ms": 2,
 3   "a": {
 4     "n": "Active Raid: Kidou Kyoushuushitsu Dai Hachi Gakari",
 5     "#": "31790"
 6   },
 7   "c": {
 8     "n": "Liko", "#": "134656"
 9   }
10 }
こう
 1 {
 2   "ms": 2,
 3   "a": {
 4     "n": "Active Raid: Kidou Kyoushuushitsu Dai Hachi Gakari",
 5     "#": 31790
 6   },
 7   "c": {
 8     "n": "Liko", "#": 134656
 9   }
10 }

JSON ファイルとして書き出す際に、この差は非常に大きいのよね。つまり JSON.parse の負担はむろん「ほんっっっっっっっっの少しだけ」減るし、JSON.parse 以前の FileReader の readAsText の負担も「ほんっっっっっっっっっっっっっっっっっの少しくらいは」減る…、というのは些細な話で、実際は「書き出された JSON を「読む」」負担が減るのが大きい。特にテキストエディタで開いて何か編集したいなんて思った際に、まぁレコード数にもよるけれど多分下手すりゃ1割削減出来ちゃう可能性すらあるので、結構違うんだよね(emacs だと確か 10MB くらいで警告が出るし、5MB 超えてたらそもそも重くて大変)。


この話がちょっとヘンというか面白いのは、「javascript であること」がポイントなことなのね。「どんな言語でも通用する話」にはあまりならない話。その話をこれからする。

javascript に限らず、「データ交換形式としての JSON」を扱うデシリアライザは、JSON が指示する型と、その言語が組み込みで持っている型とのマッチングをして、「ネイティブ型」としてデシリアライズ「してくれる」。Python のももちろんそうだし、出回ってる多くの C/C++ 向け JSON デシリアライザもやっぱり「数値は数値型に」マッピングしてくれる。なので上の例の「”#” キー」の値は、C/C++ 向け JSON デシリアライザが扱えばちゃんと「文字列なら C++ なら(たとえば) std::string、数値なら(たとえば) int」として C++ から「ちゃんと」使える。(これが JSON 表現がリッチである理由。)

これは「普通は」何を意味するかと言うと、「JSON が指示する型を変更することは、アプリケーションを破壊する」ということ。だってそうでしょ、特に C/C++、「int だと思ってたものが突如文字列に」なってたら、もうアプリケーションは改変なしには動作しない。

もうひとつは、「ネイティブ型にマッピングするということは…」、たとえば現在の Python 3 は整数型を 64bit に収めようとする。ということは…、「文字列表現 “134656” の方がコンパクト」だったりするのね。なのでメモリ使用量の見積もりは JSON 「ファイル」の見かけどおりにはならない。

ゆえ、javascript しか前提にしないのでない限りは、こういう「文字列だけど数値にしちゃえ」ということは普通は安直に考えずに、「文字列だけれどコンパクトな表現(hex やら base64 やら)を検討することのほうで「コンパクトにする」ことを考えるわけだね。(ゆえこの場合はもう、データ設計時からその特殊エンコードをデコードする処理込みで設計することになる。)

「javascript だからこそ」の話はここから。「どうせ 64bit なのでメモリ使用量削減にはならない」については同じでも、「アプリケーションが破壊される」がかなり違う。つい最近気付いて「awk の仕様を真似たのか…なんでまた」と思ったのだが、こういうことだよ:

まぢですか
1 > 1 + " a";
2 '1 a'

うん、awk だ。perl もだっけ? Python はダメだし、やってないけどおそらく Ruby では「もっとダメ」だろう。要するに「JSON としては数値として書き出しておいて、あとはそれを数値として使うか文字列のつもりで使うかは自在に行き来出来」ちゃうのよね。いいことかどうかは問わない、とにかく「それが簡単に出来る」。

無頓着に型を意識せずに、でも「文字列だと思って処理をしていた箇所」はほとんど無傷で済んじゃうんだよね。それがいやなら一度上の例のような(空文字列との)足し算しちゃえば一発で変換出来るわけだし。


まぁ「なんだかなぁ」て話かもしれんけれども。

あとこの話、思いついたので書いたけれど、現実に適用するかどうかは決めてない。書いた通りで javascript として読み込むオブジェクトとしてのメモリ使用量が減るわけではないから(むしろワタシのケースではおそらく増える)。やるとしたらむしろ hex とかそんなので削減した方がいい気がしてる。うん、多分そうするな。