V8 Node v24.15.0, V8 13.6.233.17 上でパーサーを実行して確認しました。
"[object Object]" is not valid JSON
JSON.parseは文字列を期待します。オブジェクトを渡すと、JavaScriptはまずそのオブジェクトを文字列に変換し、既定の変換は"[object Object]"というそのままのテキストを生みます。そのためパーサーは位置1の文字oで失敗します。データに問題はありません。余計なのはparseの呼び出しのほうです。
次のような書き方で見かけることもあります
- Unexpected token o in JSON at position 1
V8は2022年に、JSONのエラーメッセージのほとんどを書き換えました。検索結果に出てくるのはいまだに古い文言がほとんどですが、現行のランタイムはどれもそれを出力しません。
JSONを貼り付けて、壊れている箇所を正確に確認する
入力
貼り付けたものがブラウザの外に出ることはありません。 connect-src の許可リストにより、これは約束ではなくブラウザによる保証になっています。 自分で確かめる
実際の原因
どれが答えになることが多いか、その順に並べています。
-
01 その値はすでにパース済みだった
axios、jQuery.ajax、そして多くのAPIクライアントは、Content-Typeがそう示していればJSONを代わりにパースしてくれます。その結果をもう一度パースすると、このエラーになります。
壊れる例
const res = await axios.get('/api/user'); const user = JSON.parse(res.data); // res.data is already an object動く例
const res = await axios.get('/api/user'); const user = res.data; -
02 stringifyせずにオブジェクトを保存した
localStorageとsessionStorageは文字列を保存します。オブジェクトを渡すとString(obj)が呼ばれ、"[object Object]"というそのままのテキストが書き込まれます。その時点でデータは失われており、単に読めないだけではありません。
壊れる例
localStorage.setItem('user', user); const back = JSON.parse(localStorage.getItem('user'));動く例
localStorage.setItem('user', JSON.stringify(user)); const back = JSON.parse(localStorage.getItem('user')); -
03 テンプレート文字列にオブジェクトを埋め込んだ
`{"user": ${user}}`のようにリクエストボディを組み立てると、オブジェクトではなく"[object Object]"が挿入されます。オブジェクトを組み立てて、全体をstringifyしてください。
壊れる例
body: `{"user": ${user}}`動く例
body: JSON.stringify({ user })
よくある質問
- 「Unexpected token o in JSON at position 1」はどうなったのですか?
- 同じ間違いに対する、2022年より前のV8の文言です。そのoは"[object Object]"という文字列の2文字目でした。現在のメッセージは代わりに問題のテキストを引用しており、そのほうがはるかに有用です。
- 防御的に書くには?
- typeof value === "string" ? JSON.parse(value) : value。自分の制御が及ばない境界では妥当なガードです。ただし自分のコードの内側では、そもそもどちらが来るのかを分かっているほうが望ましいです。