JSONのパースエラー、原因はだいたいこの3つ
Unexpected token in JSON at position 4032——このエラーと何百回も戦ってわかった三大原因(末尾カンマ・クォート・見えない文字)と、エラー位置を一瞬で特定する方法。
SyntaxError: Unexpected token '}' in JSON at position 4032
深夜にこれが出たときの絶望感は、エンジニアなら共有できると思う。position 4032って、どこだよ。
JSONのパースエラーとは長い付き合いになるが、経験上、原因の9割は3つに絞られる。先に犯人リストを知っていれば、探す時間は劇的に短くなる。
犯人1: 末尾カンマ
{
"name": "clvr",
"tags": ["web", "ai"],
}
"ai"] の後ろのカンマ。JavaScriptのオブジェクトでは合法なので、コードを書く手癖でそのまま付けてしまう。JSONでは違法。
配列やオブジェクトの要素を並べ替えたり、最後の行を削除したりした直後に発生しがちで、package.jsonを手で編集したあとのnpm installが謎に失敗するときは、まずこれを疑っていい。
逆パターンの「カンマ忘れ」も同罪。行を追加したときに前の行のカンマを付け忘れるやつで、エラー位置が「次の行の先頭」を指すので少し混乱する。
犯人2: クォートまわり
JSONの文字列とキーはダブルクォートのみ。シングルクォートは使えないし、キーのクォート省略もできない。
{ 'name': 'clvr' } ← 全部ダメ
{ name: "clvr" } ← キーがダメ
これもJavaScriptの手癖がそのまま事故になるパターン。もうひとつ厄介なのが、WordやSlackから値をコピペしたときに紛れ込む「スマートクォート」(" と ")。見た目はほぼダブルクォートなのに別の文字なので、目視ではまず気づけない。
犯人3: 見えない文字
一番タチが悪い。過去に実際に踏んだものを挙げると——
- BOM。WindowsのエディタやAPIレスポンスの先頭に付いてくる不可視文字。
position 0でエラーが出たらほぼこれ - 全角スペース。設定ファイルをインデントしようとして混入。エディタ上では半角スペースと見分けがつかない
- 改行コードや制御文字が文字列の中に生で入っているケース。ログをコピペしたときに多い
以前、同僚の環境でだけCIが落ちる事件があって、2時間調べた結果、設定JSONの中の全角スペース1個だった。ああいう時間は二度と過ごしたくない。
position 4032を人力で数えない
エラーメッセージのpositionは「先頭から何文字目か」でしかないので、そのままでは使いものにならない。エディタに貼って先頭から数える……のは無理なので、位置から行と列を割り出す必要がある。
やり方は単純で、position番目までの文字列に含まれる改行を数えれば行番号が出る。ただ、この作業を毎回やるのが面倒すぎて、ツールにした。
壊れたJSONを貼って「検証」を押すと、エラーの行・列と、その周辺の文字列を表示する。スマートクォートも全角スペースも、周辺表示を見れば一発でわかる。ついでに整形(インデント2/4/タブ)、圧縮、キーのアルファベット順ソートもできる。処理はブラウザ内で完結するので、APIキーが入ったままの設定ファイルを貼っても外に漏れない。
コマンドライン派ならjqでも同じことができる。
jq '.' config.json # 正常なら整形して出力、壊れていればエラー位置を表示
CIに組み込むならこっちが便利で、JSONを生成するスクリプトの後ろにjq emptyを挟んでおくと、壊れたJSONがデプロイまで流れていく事故を防げる。
最後にひとつ。「JSONにコメントを書きたい」と思ったことがあるなら、それは正常な感覚だが、JSONの仕様上は書けない。VS Codeの設定ファイルなどで見かけるコメント付きのやつはJSONCという別物なので、あれを普通のパーサーに食わせると当然死ぬ。これも地味によくある事故なので、頭の隅に置いておいてほしい。