JSON入門:構文ルールとJavaScript・PythonでのJSONの扱い方
JSONは、最もシンプルで最も広く使われているデータ交換形式です。いくつかの短いルールと、各言語に2つずつある関数を覚えれば、どんなデータでも読み込み、作成し、保存できます。
API、モバイルアプリ、あるいはプロジェクトの設定ファイルを扱っていれば、遅かれ早かれJSONに出会います。JSONは、データを保存したりやり取りしたりするためのテキスト形式で、人が読むことができ、どのプログラミング言語でも簡単に処理できます。このチュートリアルでは、まず定義と構文ルールを説明し、実際の例を見たうえで、JavaScriptとPythonでのJSONの読み書きに進みます。その際、アクセント付きの文字や日本語のような英語以外のテキストの扱いに特に注目します。最後に、よくあるエラーと、JSON整形ツールを使った原因の突き止め方を紹介します。このページのコードはすべて実際に実行したもので、出力も本物です。
JSONとは
JSONはJavaScript Object Notationの略です。構文はJavaScriptでのオブジェクトの書き方に由来しますが、特定の言語には依存しません。Python、PHP、Java、C#、Goをはじめ、ほぼすべての言語にJSONを扱うための既製のツールがあります。JSONはRFC 8259とECMA-404で規格化されており、ファイルの拡張子は.json、Webでのメディアタイプはapplication/jsonです。主な用途は次のとおりです。
- Web API:現代のサービスの多くは、リクエストとレスポンスをJSONで送ります。
- 設定ファイル:Node.jsプロジェクトの
package.jsonや、PHPのcomposer.jsonなどです。 - データの保存:ブラウザーの
localStorage、PostgreSQLやMySQLといったデータベースのJSON型カラム、ドキュメント型データベースで使われます。
JSONの構文ルール
JSONのドキュメントはすべて、互いに入れ子にできる6種類の値から組み立てられます。
| 型 | 例 | 説明 |
|---|---|---|
| object(オブジェクト) | {"city": "Lisbon"} | 波括弧で囲んだキーと値のペア |
| array(配列) | [1, 2, 3] | 角括弧で囲んだ、順序のある値のリスト |
| string(文字列) | "hello" | ダブルクォートで囲んだテキスト |
| number(数値) | 42, -3.5, 1.2e6 | 整数または小数。クォートは付けない |
| boolean(真偽値) | true, false | 小文字のみ |
| null | null | 値がないことを表す |
ほとんどのエラーの原因になるルールは次のとおりです。
- キーは必ずダブルクォートで囲んだ文字列です。
{"name": "Ali"}は正しく、{name: "Ali"}は誤りです。 - 使えるのはダブルクォートだけです。シングルクォートで囲んだ
'Ali'は、有効なJSONではありません。 - 末尾のカンマは使えません。オブジェクトや配列の最後の要素のあとにカンマを置くことはできません。
- コメントはありません。
//も/* */も使えません。 - 言語固有の値は使えません。
undefined、NaN、Infinity、True、Noneは有効なJSONではありません。 - 数値をゼロで始めることはできません。
012は無効です。そのため電話番号、郵便番号などのIDは、"0123456789"のように文字列として書くべきです。こうすれば先頭のゼロも保たれます。 - 文字列中の特殊文字はエスケープします。ダブルクォートは
\"、バックスラッシュは\\、改行は\n、任意のUnicode文字は\uXXXXです。 - スペースと改行は、各部分の間では意味を持ちません。だからこそJSONは、1行に詰めて書くことも、きれいにインデントして書くこともできます。
実例:ネットショップの注文データ
{
"orderId": 10245,
"createdAt": "2026-09-17T10:30:00Z",
"customer": {
"name": "Maria",
"phone": "0123456789",
"city": "Lisbon"
},
"items": [
{ "title": "Python for Beginners", "price": 32, "qty": 1 },
{ "title": "Wireless Mouse", "price": 45, "qty": 2 }
],
"paid": true,
"discountCode": null
}
この例には、すべての種類の値が含まれています。customerは入れ子になったオブジェクトで、itemsはオブジェクトの配列です。価格は数値ですが、電話番号は先頭のゼロを保つために文字列にしています。JSONには「日付」型がないため、注文日時は標準的なISO 8601形式の文字列です。そしてdiscountCodeはnullを使って、この注文に割引コードがなかったことを表しています。2番目の商品の価格を読むには、JavaScriptではorder.items[1].price、Pythonではorder["items"][1]["price"]と書きます。
JavaScriptでJSONを扱う
JavaScriptには、ほとんどの作業をこなせる組み込み関数が2つあります。JSON.parseはJSONテキストをオブジェクトに変換し、JSON.stringifyはオブジェクトをJSONテキストに変換します。
JSON.parseでJSONを読み込む
const text = '{"name": "Maria", "age": 28, "skills": ["JavaScript", "Python"], "active": true}';
const user = JSON.parse(text);
console.log(user.name); // Maria
console.log(user.skills[0]); // JavaScript
console.log(user.active); // true
JSON.stringifyでJSONを作る
const product = { title: "Café Guide", price: 32, tags: ["travel", "bestseller"], discount: null };
console.log(JSON.stringify(product));
console.log(JSON.stringify(product, null, 2));
1回目の呼び出しは、ネットワーク送信に向いた1行のコンパクトなJSONを返します。3番目の引数に2を渡した2回目の呼び出しは、スペース2つでインデントした読みやすい出力を返します。2番目の引数は値の絞り込みや変換に使うもので、通常はnullのままにします。出力は次のとおりです。
{"title":"Café Guide","price":32,"tags":["travel","bestseller"],"discount":null}
{
"title": "Café Guide",
"price": 32,
"tags": [
"travel",
"bestseller"
],
"discount": null
}
アクセント付きの文字やその他の非ASCIIテキストは、JSON.stringifyの出力でもそのまま残り、\uエスケープには変換されません。
APIからJSONを取得してlocalStorageに保存する
async function loadProduct() {
const res = await fetch("https://api.example.com/products/1");
return await res.json();
}
localStorage.setItem("cart", JSON.stringify({ items: [101, 205] }));
const cart = JSON.parse(localStorage.getItem("cart"));
console.log(cart.items.length); // 2
res.json()メソッドはそれ自体がレスポンスをパースしてオブジェクトを返すので、その結果を再びJSON.parseに渡してはいけません。localStorageは文字列しか保存できないため、オブジェクトは保存時にJSON.stringifyで、読み出し時にJSON.parseで変換します。
JSON.stringifyで変わってしまう値
const data = { a: undefined, b: () => 1, c: NaN, d: new Date(Date.UTC(2026, 8, 17)) };
console.log(JSON.stringify(data));
// {"c":null,"d":"2026-09-17T00:00:00.000Z"}
値がundefinedや関数のキーは何も言われずに削除され、NaNとInfinityはnullになり、日付はISO形式の文字列になります。この結果を再びパースしても、日付は文字列のままで、自動的にDateに戻ることはありません。
エラーの処理
try {
JSON.parse("{'name': 'Ali'}");
} catch (err) {
console.log(err.name); // SyntaxError
console.log(err.message); // Expected property name or '}' in JSON at position 1 (line 1 column 2)
}
無効なテキストはすべてSyntaxErrorを投げるので、外部から来たデータはtry...catchの中で処理してください。上のメッセージはNode.jsの出力で、Chromeと同じです。FirefoxとSafariは同じエラーを別の言い回しで報告します。
PythonでJSONを扱う
Pythonでは標準のjsonモジュールがすべてを担当し、インストールは不要です。主な関数は4つあります。json.loadsとjson.dumpsは文字列を扱い、json.loadとjson.dumpはファイルを直接扱います。前の2つに付いているsは、string(文字列)のsです。
JSON文字列を辞書に変換する
import json
text = '{"name": "Maria", "age": 28, "skills": ["JavaScript", "Python"], "active": true}'
user = json.loads(text)
print(user["name"]) # Maria
print(type(user)) # <class 'dict'>
print(user["active"]) # True
| JSON | Python | JavaScript |
|---|---|---|
| object | dict | Object |
| array | list | Array |
| string | str | String |
| number | intまたはfloat | Number |
true / false | True / False | true / false |
null | None | null |
データをJSONに変換する:非ASCII文字の問題
product = {"title": "Café Guide", "price": 32, "tags": ["travel", "bestseller"], "discount": None}
print(json.dumps(product))
print(json.dumps(product, ensure_ascii=False, indent=2))
出力の1行目には驚くかもしれません。
{"title": "Caf\u00e9 Guide", "price": 32, "tags": ["travel", "bestseller"], "discount": null}
json.dumpsは既定で、非ASCII文字をすべて\uXXXXの形で書き出します。そのためéは\u00e9になり、ほかの文字体系のテキストも同じように1文字ずつエスケープされます。日本語のテキストなら、ひらがなも漢字もすべてこの形になります。この出力は有効で、どのプログラムも正しく読めますが、人には読めず、容量も余計に使います。ensure_ascii=Falseを指定すればテキストがそのまま書き出され、indent=2でJavaScriptの例と同じように出力が整形されます。
JSONファイルの読み書き
with open("product.json", "w", encoding="utf-8") as f:
json.dump(product, f, ensure_ascii=False, indent=2)
with open("product.json", encoding="utf-8") as f:
loaded = json.load(f)
print(loaded["tags"][0]) # travel
重要な点が2つあります。json.dumpにもensure_ascii=Falseを渡すこと、そしてファイルは必ずencoding="utf-8"で開くことです。Windowsでエンコーディングを指定しないと、PythonはUTF-8ではないシステムの既定エンコーディングを使うことがあり、その結果UnicodeEncodeErrorが発生するか、文字化けだらけのファイルができあがります。
printの出力はJSONではない
print(product)
# {'title': 'Café Guide', 'price': 32, 'tags': ['travel', 'bestseller'], 'discount': None}
よくある間違いが、辞書をprintした出力をJSONとしてどこかに貼り付けることです。このテキストはPython独自のデータ表現で、シングルクォートが使われ、nullではなくNoneになっているため、どのJSONパーサーも受け付けません。JSONを作るときは、必ずjson.dumpsを使ってください。
Pythonでのエラー処理
try:
json.loads("{'name': 'Ali'}")
except json.JSONDecodeError as e:
print(e) # Expecting property name enclosed in double quotes: line 1 column 2 (char 1)
json.JSONDecodeErrorはValueErrorのサブクラスで、lineno、colno、pos属性でエラーの位置がわかります。requestsライブラリーを使っているなら、response.json()がAPIのレスポンスに対して同じ変換を行います。細かな違いが1つあります。Pythonは既定で、標準のJSONではないNaNを入力として受け付け、出力にも書き出します。これを防ぐには、json.dumpsにallow_nan=Falseを渡してください。
よくあるJSONのエラーとメッセージの意味
| 間違い | 無効な例 | JavaScriptのメッセージ(Node.jsとChrome) | Pythonのメッセージ |
|---|---|---|---|
| シングルクォート、またはクォートのないキー | {'name': 'Ali'} | Expected property name or '}' | Expecting property name enclosed in double quotes |
| オブジェクト末尾のカンマ | {"a": 1,} | Expected double-quoted property name | Python 3.14:Illegal trailing comma before end of object、Python 3.12:Expecting property name enclosed in double quotes |
| 配列末尾のカンマ | [1, 2,] | Unexpected token ']' | Python 3.14:Illegal trailing comma before end of array、Python 3.12:Expecting value |
| カンマの抜け、または値のあとのコメント | {"a": 1 "b": 2} | Expected ',' or '}' after property value | Expecting ',' delimiter |
| PythonやJavaScriptの値 | {"a": True} | Unexpected token 'T' | Expecting value |
| 先頭がゼロの数値 | {"phone": 0123456789} | Unexpected number | Expecting ',' delimiter |
| 閉じていない文字列 | {"a": "x} | Unterminated string | Unterminated string starting at |
| 空の入力 | 空のテキスト | Unexpected end of JSON input | Expecting value |
メッセージには通常、position 8やline 1 column 9のように位置も含まれ、本当の問題はその少し手前にあることがよくあります。JavaScriptのメッセージがUnexpected token '<'で始まっていたら、JSONではなくHTMLページ、たとえば404エラーページやログインページを受け取っている可能性が高いです。このケースはUnexpected tokenエラーのガイドで詳しく解説しています。BOM付きで保存されたファイルは、PythonでUnexpected UTF-8 BOMというエラーになります。encoding="utf-8-sig"で開くか、BOMなしで保存し直してください。
JSON整形ツールで整形・検証する
長いJSONが1行で届いたときや、パースできない理由がわからないときは、JSON整形ツールを使うのが一番の近道です。このツールはブラウザー自身のJSONパーサーで動作し、データがブラウザーの外に出ることはないので、トークンやユーザーデータを含むAPIレスポンスも安心して調べられます。手順は次のとおりです。
- JSONを入力JSON欄に貼り付けます。すぐに整形され、整形ボタンでも同じ処理ができます。
- インデントメニューでインデントを選びます。スペース2つ、スペース4つ、タブから選べます。
- JSONが無効な場合は、
無効なJSONです:で始まるメッセージにブラウザーのエラーテキストが表示され、通常は問題の位置も含まれます。上の表で意味を確認し、修正してください。 - ツリータブではデータを折りたたみ可能なツリーとして表示でき、すべて展開とすべて折りたたむですべての枝を開閉できます。大きなレスポンスの中からキーを探すのにとても便利です。
- データをコンパクトに送信・保存したいときは、圧縮でスペースと改行をすべて取り除けます。結果はコピーでコピーします。
受け取るJSONに型が必要なら、JSON型定義ジェネレーターがサンプルからTypeScript、Go、Pythonの定義を生成します。手順はTypeScriptでAPIレスポンスに型を付ける方法のガイドで詳しく説明しています。