【JavaScript】静的なJSONファイルを楽に読む

 時折、静的なJSONファイルを読む必要があるプログラムがあります。ここでいう静的とはプログラムが完成した時点で変更されることのない、ぐらいの意味です。そういったJSONファイルをJavaScript上で読む場合、実は大変楽な方法があります。例は次です。

// jsonReader.js
const JSONContent = require('package.json');
console.dir(JSONContent);

/** 実行結果 */
// {
//     private: true,
//     scripts: {
//         dev: 'npm run development',
//         development: 'cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js',
//         watch: 'npm run development -- --watch',
//         'watch-poll': 'npm run watch -- --watch-poll',
//         hot: 'cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js',
//         prod: 'npm run production',
// ...
// ...

 denoやts-nodeなどでimportを使う場合も同様にファイルを直接参照してdefault export相当として扱うと中身をオブジェクトとして読み込めます。JavaScriptファイルらしい拡張子をつけたり、 fs.readfile で文字列として取得した後に JSON.parse する、などといった細かい作業は不要です。
 細かい仕組みは調べていませんがJSONファイルの定義が次であるためJavaScript同様に依存関係関連でサポートされているのだと思います。

あらゆる JSON のテキストは有効な JavaScript の式です…
…ただし、 proposal to make all JSON text valid ECMA-262 を実装した JavaScript エンジンのみでの話です。この提案を実装していないエンジンでは、 U+2028 LINE SEPARATOR と U+2029 PARAGRAPH SEPARATOR は JSON の文字列リテラルとプロパティのキーでは許されますが、これらの機能を JavaScript 文字列リテラルの中で使用すると SyntaxError になります。

JSON – JavaScript | MDN

 ちなみに変更される可能性があり、複雑なこと(関数実行など。文字や数字の設定値のような読み込みのみなら無問題)を任せるJSONファイルを読み込むコードを安易に記述するのは危険です(例えばNode.jsによるサーバサイドプログラム)。これは次の様なプロトタイプ汚染による攻撃を招き、任意コード実行にもつながりかねません。もしその様なコードを記述する場合は攻撃への対策をきっちり調べてしっかりしておきましょう。

// tmp.json
{
    "__proto__": {
        "a": 1,
        "toString": "any"
    },
    "b": 2
}
// tmp.jsonここまで
// reader.js
const plainObj = {
    __proto__: {a: 1},
    b: 2,
};
// プレーンオブジェクトのプロトタイプは、コード内で割り当てられたものです。.
console.log(plainObj.__proto__.a === 1); // true
const plainObjProto = Object.getPrototypeOf(plainObj);
console.log(plainObjProto !== Object.prototype); // true
console.log(plainObjProto.a === 1); // true

const JSONContent = require('./tmp.json');
// 解析されたオブジェクトには __proto__ というプロパティがあり、
// __proto__ ゲッターとセッターをオーバーライドしていますが、
// そのプロトタイプは Object.prototype のままです。
console.log(JSONContent.__proto__.a === 1); // true
// プレーンオブジェクトと同じ構造のJSONコンテンツで↑と同じ真偽値を見ます
const jsonObjProto = Object.getPrototypeOf(JSONContent);
console.log(jsonObjProto !== Object.prototype); // false
console.log(jsonObjProto.a === 1); // false


// 要するに何が危険かというと, 開発者が期待していないモノが優先して呼び出される危険があります
// tmp2.json
{
  "b": 2
}
// tmp2.jsonここまで
const JSONContent2 = require('./tmp2.json');
console.log(JSONContent2.__proto__.toString); // [Function: toString]
console.log(JSONContent.__proto__.toString); // 'any'
>株式会社シーポイントラボ

株式会社シーポイントラボ

TEL:053-543-9889
営業時間:9:00~18:00(月〜金)
住所:〒432-8003
   静岡県浜松市中央区和地山3-1-7
   浜松イノベーションキューブ 315
※ご来社の際はインターホンで「316」をお呼びください

CTR IMG