JSONでBigIntがうまく扱えないのなんで?

# はじめに

でかい数字をJSONで表現するとどうなるんだろう

```JavaScript
// number
> JSON.stringify({ "a": 911232123123127987297})
'{"a":911232123123128000000}'
> JSON.parse(`{ "a": 911232123123127987297}`)
{ a: 911232123123128000000 }

// BigInt
> JSON.stringify({ "a": 911232123123127987297n})
Uncaught TypeError: Do not know how to serialize a BigInt
    at JSON.stringify (<anonymous>)
    at <anonymous>:2:6
> JSON.parse(`{ "a": 911232123123127987297n}`)
Uncaught SyntaxError: Expected ',' or '}' after property value in JSON at position 28
    at JSON.parse (<anonymous>)
    at <anonymous>:2:6
```

`Do not know how to serialize a BigInt` :thinking: 

なんかBigIntはうまく扱えないようです。なんででしょ


# BigIntについて

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/BigInt

MDNみるのが一番良い。

[JSONのくだりも書いてある](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/BigInt#json_%E3%81%A7%E3%81%AE%E4%BD%BF%E7%94%A8)

> JSON.stringify() を BigInt 値に対して使用すると TypeError が発生します。 BigInt 値は既定で JSON のシリアライズに対応していないためです。ただし、 replacer 引数を JSON.stringify で使用すると、 BigInt のプロパティをエラーなしでシリアライズすることができます。

> 長整数になる値が含まれると思われる JSON データがある場合は、 reviver 引数を JSON.parse で使用することで取り扱うことができます。

なんで自分でパースとシリアライズ定義時にいちいち変換かけないかんの？

# JSONでのBigIntサポートは議論されたが互換性が優先された

https://esdiscuss.org/topic/json-support-for-bigint-in-chrome-v8

https://github.com/tc39/proposal-bigint/issues/162

https://github.com/tc39/proposal-bigint/issues/24

要点絞ると

- JSON仕様は[RFC 8259](https://www.rfc-editor.org/rfc/rfc8259.html)と[ECMA-404 2nd](http://www.ecma-international.org/publications/standards/Ecma-404.htm)でFIXしており、BigIntについての取り扱い方が定義されてない
- パーサーやシリアライザの後方互換性を考えるとChrome/V8や一部の実行環境だけパース可能にしてもデータのポータビリティが失われてしまう
- そもそも JSON文字列中の数値がnumberなのかbigintなのかを判別する仕様がJSON自体に存在してない
- [日付型とかも文字で表現して回避してる](https://esdiscuss.org/topic/json-support-for-bigint-in-chrome-v8#content-42)し、前述のようにreviver/replacerで吸収するのが混乱が一番少ない
- [新しいJSON拡張フォーマットとその処理系に期待しても良い。けど、議論時点では互換性とJSONとしての仕様準拠を重視した](https://esdiscuss.org/topic/json-support-for-bigint-in-chrome-v8#content-18)

という大激論の末、BigIntはJSONでうまく扱われない子になってしまったようです

# JSONでBigIntを扱えるようにしようとする活動もろもろ

## JSOX

https://github.com/d3x0r/JSOX

J-SOXと似ててググらビリティが悪い。[jsonxっていう全然用途が違うもの](https://www.npmjs.com/package/jsonx)もあるので注意

[JSON5](https://json5.org/)/[JSON6](https://github.com/d3x0r/JSON6) といった拡張JSONフォーマットを提案している方がBigInt対応も考慮した拡張JSONフォーマットとして定義
プロポーザル的な立ち位置でJSON拡張フォーマットとしてBigIntをうまく扱えるようにしようとしている

```deno
import { JSOX } from "npm:jsox"

const bigintObj = { a: 8982979873927987392837212139794n }

console.log(JSOX.stringify(bigintObj))
// {a:8982979873927987392837212139794n}
console.dir(JSOX.parse(JSOX.stringify(bigintObj)))
// { a: 8982979873927987392837212139794n }
```

jsoxは `n` 表記でシリアライズされるしパースするとちゃんとBigIntとして扱う

## json-bigint

https://github.com/sidorares/json-bigint

https://www.npmjs.com/package/json-bigint

`JSONbig.parse`, `JSONbig.stringify` といったネームスペースが増える

ただし2年くらい更新止まってる

## json-bigint-native

https://github.com/vogievetsky/json-bigint

https://www.npmjs.com/package/json-bigint-native

前述の `json-bigint` をフォークして作っていて比較的手も入ってる

```deno
import JSONbig from "npm:json-bigint-native"

const bigintObj = { a: 8982979873927987392837212139794n }

console.log(JSONbig.stringify(bigintObj))
// {"a":8982979873927987392837212139794}
console.dir(JSON.parse(JSONbig.stringify(bigintObj)))
// { a: 8.982979873927987e+30 }
```

シリアライズした結果は `123n` じゃなくてそのままでかいDigitが入る
パースすると指数表記なんでnumberになっているっぽい

# 余談 - JSONの拡張フォーマット色々

## JSONC

コメントが書けるJSON

https://github.com/microsoft/node-jsonc-parser

VSCodeの設定とかはこれ

## JSON-Scheme

JSONに型情報を持ち込むやつ

https://json-schema.org/

## CBOR/BSON

JSONをバイナリで表現しようぜのやつ

### CBOR

https://cbor.io/

https://qiita.com/yoshii0110/items/d975610b4173043c77ca

### BSON

https://bsonspec.org/