大容量JSONファイルのブラウザでの読み込み

# ローカルにある大容量のJSONファイルをブラウザで読みたい
## 前提
ブラウザの文字列変数には使用上の上限があるらしい。
(https://qiita.com/mod_poppo/items/f3fcbc673526c84b9387)
1GBのJSONファイルをJSON.parseで解釈しようとするとエラーを吐く。
（実際にはその全段階で文字列変数に突っ込んだ段階でエラー）
Googleのサービス（ロケーション等）でダウンロードしてJSONなどは平気で数百メガバイトいくため何とかしよう。
以下のサンプルはGoogleサービスでよくみるJSONファイルを前提としています。

※当然搭載メモリによる上限はあります。今回はあくまでブラウザの仕様上の上限突破を目指します。

## ちょっとづつ読み込んで配列に突っ込む
GoogleのJSONは1行に一つしか'{'or'}'がない。
・・・ということでまずは、一行ずつJSONファイルを読み込む

### FileReaderクラスを拡張してバイト単位の読出しに対応
```javascript:
class LargeFileReader extends FileReader{
  constructor(){
    super();
  }
  readAs(blob, ctx){
    return new Promise((res, rej) => { 
      super.addEventListener('load', ({target}) => res(target.result));
      super.addEventListener('error', ({target}) => rej(target.error));
      super[ctx](blob);
    });
  }
  readAsArrayBuffer(blob){
    return this.readAs(blob, 'readAsArrayBuffer');
  }
  readAsText(blob){
    return this.readAs(blob, 'readAsText');
  }
  readAsDataURL(blob){
    return this.readAs(blob, 'readAsDataURL');
  }
}
```
### 巨大ファイルを改行区切りで読み込みます
改行はCR+LF or LFに対応
```javascript
  async asyncReadLargeText(f, encode){
    const result = [],
      td = new TextDecoder(encode),
      re = new RegExp('\r?\n');
    //Stringの配列を返す
    //Return String array.
    function blob2arrayText(remain, b = new Uint8Array()){
      const nowBlob = new Uint8Array(remain.length + b.length)
      nowBlob.set(remain);
      nowBlob.set(b, remain.length);
      const arr = Uint8Array.from(nowBlob),
        dec = td.decode(arr),
        lines = dec.split(re);
      return lines;
    }
    //指定バイトづつ読込
    //Reading text file per specified byte size.
    const chunkSize = 1024*1024*500;
    //各読み出し単位毎の最後の改行以降のデータを格納
    //Stores the data after the last newline for each read unit.
    let remainBlob = new Uint8Array(),
    //whileループにおける、処理取りこぼしの検知用 (取りこぼしがない場合は-1)
    //For detecting undone operations in the while loop. (-1 if no undone)
      noNewLine = -1,
      offset = 0;
    while(offset < f.size){
      noNewLine = offset;
      //指定バイトのblob作成
      const slice = f.slice(offset, offset + chunkSize);
      offset += chunkSize;
      const buffer = await readAsArrayBuffer(slice).catch( e => console.log(e)),
        view = new Int8Array(buffer);
      for(let i = view.length-1; i >= 0; i--){
        //バイト列の後方から改行をさがす。
        //Search for newlines from the back of the byte sequence.
        const v = view[i];
        if(v === 13 || v === 10){//  \r = 13 , \n = 10
          //最後の改行より前部分を切り取り（改行含む）
          const newBlob = view.slice(0, i+1),
            lines = blob2arrayText(remainBlob, newBlob);
          for(let i=0, len=lines.length; i < len; i++){
            if(lines[i] !== '') result.push(lines[i]);
          }
          //最後の改行以降を保存して次回ループ時に結合
          remainBlob = view.slice(i+1);
          noNewLine = -1;
          break;
        }
      }
    }
    if(remainBlob.length > 0){
      blob2arrayText(remainBlob);
      noNewLine = -1;
    }
    //読み出し単位データ内に改行がない場合の補完
    if(noNewLine >= 0){
      const slice = f.slice(noNewLine),
        buffer = await rd.readAsArrayBuffer(slice).catch( () => result);
      blob2arrayText(remainBlob, new Int8Array(buffer));
    }
    return result;
  }//end of AsyncReadLargeText
```
読み込んだ行ごとの配列を一個づつのJSONへ組み立てなおします。
{}の出現回数をカウントして、一塊のJSONができ次第、パースしています。
```javascript:readjsonfile.js
readjsonfile(txtArr){
    //JSONファイルのルートキーを指定
    //今回はGoogleのロケーション履歴を例にしている
    const key = 'locations';
    const arr = {[key]:[]};
    let recode = '{',
      k = 1;
    //let curl=0;//一行に複数個{}がある場合を想定した動き、今回はコメントアウト
    for(let i=2, len=txtArr.length; i<len; i++){
      const s = txtArr[i];
      //const part = s.split('}');
      //一行に{}は各一個までの想定
      if(s.indexOf('}') !== -1) k--;
      if(k === 0){
        recode = recode + '}';
        try{
          //一つの配列ごとにJSONを組み立ててパース
          const js = JSON.parse(recode);
          if(Object.keys(js).length > 0) arr[key].push(js);
        }catch(e){
          continue;
        }
        recode = '{';
        k++;
        continue;
      }
      if(s.indexOf('{') !== -1) k++;
      //curl = curl - (part.length - 1);
      //if(curl > 0) recode = recode + s;
      recode = recode + s;
      //curl = curl + (s.split('{').length - 1);
    }
    return arr;
  }
```
これでルートキー以下の要素が一つづつ収まった配列が出来上がります。