2026年6月3日
2026年6月3日
PHPのJSON処理の使い方|json_encode・json_decodeのオプションとエラー対処、WordPressでの書き方
PHPでJSONを扱うときは、配列やオブジェクトをJSON文字列にする json_encode() と、JSON文字列をPHPの値に戻す json_decode() の2つを使います。実務でつまずくのは、日本語が \u3042 のように変換される、壊れたJSONが null になって気づかない、空の配列とオブジェクトの区別が崩れる、といった点です。この記事では、2つの関数の基本と主なオプション、エラーの検出方法、APIでJSONを受け渡すときの書き方、WordPressで使う専用関数までを説明します。
json_encode:PHPの値をJSONにする
<?php
$data = [
'id' => 12,
'title' => 'こんにちは',
'url' => 'https://example.com/post/12',
'tags' => ['php', 'json'],
'price' => 10.0,
];
echo json_encode($data);
// {"id":12,"title":"\u3053\u3093\u306b\u3061\u306f","url":"https:\/\/example.com\/post\/12","tags":["php","json"],"price":10}
既定では、日本語は \uXXXX の形に、/ は \/ に変換されます。どちらもJSONとしては正しく、受け取った側で元に戻りますが、ログやファイルで人が読むには不便です。オプション(フラグ)を | でつないで指定すると、出力を調整できます。
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
よく使うフラグは次のとおりです。
- **
JSON_UNESCAPED_UNICODE** … 日本語などをそのまま出力します。 - **
JSON_UNESCAPED_SLASHES** …/をエスケープしません。ただし、HTMLの<script>内に埋め込む場合は後述の理由で使い方に注意が必要です。 - **
JSON_PRETTY_PRINT** … 改行とインデントを入れて読みやすくします。APIの応答では通信量が増えるので、確認用途に向いています。 - **
JSON_PRESERVE_ZERO_FRACTION** …10.0を10ではなく10.0のまま出力します。受け取り側で整数と小数を区別したい場合に使います。 - **
JSON_THROW_ON_ERROR** … 失敗したときにfalseを返す代わりにJsonExceptionを投げます(後述)。
JSON_NUMERIC_CHECK(数字に見える文字列を数値にする)は、"0120" のような電話番号や郵便番号の先頭の0が消えるため、使う場面を選びます。
配列が「配列」になるか「オブジェクト」になるか
PHPの配列は、キーが0から連番なら [...](JSONの配列)、それ以外なら {...}(JSONのオブジェクト)になります。ここが思わぬ不具合の原因になります。
$list = [10, 20, 30];
$list = array_filter($list, fn($v) => $v > 10); // キーが 1, 2 になる
echo json_encode($list); // {"1":20,"2":30} ← オブジェクトになる
echo json_encode(array_values($list)); // [20,30]
array_filter() や unset() の後はキーに抜けができるため、配列として返したいなら array_values() で振り直します。逆に、中身が空でもオブジェクト {} として返したい場合は、(object) [] や new stdClass() を渡すか、JSON_FORCE_OBJECT を使います(このフラグは入れ子の配列もすべてオブジェクトにします)。
クラスのオブジェクトをJSONにする
オブジェクトは既定で public なプロパティだけが出力されます。出力内容を自分で決めたい場合は JsonSerializable を実装します。パスワードのハッシュなど、外に出してはいけない値を誤って含めないためにも有効です。
class User implements JsonSerializable
{
public function __construct(
private int $id,
private string $name,
private string $passwordHash,
) {}
public function jsonSerialize(): array
{
return ['id' => $this->id, 'name' => $this->name]; // passwordHash は出さない
}
}
json_decode:JSONをPHPの値に戻す
$json = '{"id":12,"title":"こんにちは","tags":["php","json"]}';
$obj = json_decode($json); // stdClass オブジェクト:$obj->title
$arr = json_decode($json, true); // 連想配列:$arr['title']
第2引数を true にすると連想配列で返ります。配列のほうが isset() や ?? で扱いやすいため、外部から受け取ったデータは配列で受けることが多いです。
注意点として、JSONの文字列 null を読み込んだ結果も、壊れたJSONを読み込んだ結果も、どちらも null になります。戻り値だけでは成功と失敗を区別できないため、次のエラー検出が欠かせません。
大きな整数(例えば外部サービスの64ビットのID)は、PHPの整数の範囲を超えると小数に変換されて値が変わることがあります。その場合は JSON_BIGINT_AS_STRING を第4引数に指定すると文字列として受け取れます。
エラーを検出する
PHP 7.3以降なら、JSON_THROW_ON_ERROR を指定して例外で受けるのが分かりやすい方法です。
try {
$data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
error_log('JSON decode failed: ' . $e->getMessage());
$data = [];
}
try {
$out = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
error_log('JSON encode failed: ' . $e->getMessage());
}
このフラグを使わない場合は、呼び出した直後に json_last_error() と json_last_error_msg() で確認します。JSON_THROW_ON_ERROR を付けたときは json_last_error() の値は更新されないので、両方を混ぜないようにします。
PHP 8.3以降では、デコードせずに形式が正しいかだけを調べる json_validate() も使えます。
よく出るエラーと原因は次のとおりです。
**Syntax error**JSONの書式が正しくありません。末尾のカンマ([1,2,])、シングルクォート({'a':1})、キーに引用符がない、などがよくある原因です。ファイルから読んだ場合は、先頭にUTF-8のBOMが付いていないかも確認します。
**Malformed UTF-8 characters, possibly incorrectly encoded**json_encode() に渡した文字列にUTF-8として正しくないバイトが含まれています。Shift_JISのCSVやデータベースから読んだ値で起きやすいため、mb_convert_encoding($str, 'UTF-8', 'SJIS-win') などで変換してから渡します。原因を直せない場合の応急処置として、不正な文字を置き換える JSON_INVALID_UTF8_SUBSTITUTE もあります。
**Maximum stack depth exceeded**入れ子が深さの上限(既定512)を超えています。通常のデータでは起きにくいため、循環参照など構造の問題を先に疑います。
APIでJSONを受け取る・返す
フォームではなくJSONの本文(Content-Type: application/json)で送られてきたデータは、$_POST には入りません。php://input から本文を読み込んでデコードします。
<?php
$raw = file_get_contents('php://input');
try {
$body = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
http_response_code(400);
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['error' => 'invalid_json']);
exit;
}
$name = is_string($body['name'] ?? null) ? trim($body['name']) : '';
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['ok' => true, 'name' => $name], JSON_UNESCAPED_UNICODE);
デコードできたからといって中身が想定どおりとは限りません。必要なキーがあるか、型が正しいかを確認してから使います。応答では Content-Type ヘッダーを付け、JSONの前後に余計な出力(echo のデバッグ表示や警告メッセージ)が混ざらないようにします。混ざると、JavaScript側の response.json() が解析エラーになります。
HTMLの中にJSONを埋め込むとき
<script> タグ内にJSONを書き出す場合、値に </script> が含まれるとタグがそこで閉じてしまい、XSSの原因になります。JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT を付けると、<・>・&・引用符が \u003C のような形に変換され、安全に埋め込めます。
WordPressでJSONを扱う場合
WordPressには、JSON用の関数が用意されています。
- **
wp_json_encode()** …json_encode()の代わりに使います。文字コードの問題があったときに整えてから変換を試みるなど、WordPress向けの配慮が入っています。 - **
wp_send_json_success()/wp_send_json_error()** … Ajaxの応答用です。{"success":true,"data":...}の形で出力し、ヘッダーの付与と処理の終了まで行います。 - REST API … コールバックから配列か
WP_REST_Responseを返せば、WordPressがJSONに変換します。受け取ったJSON本文は$request->get_json_params()で取得できます。
<?php
add_action( 'wp_ajax_my_get_stats', function () {
check_ajax_referer( 'my_stats_nonce', 'nonce' );
if ( ! current_user_can( 'edit_posts' ) ) {
wp_send_json_error( [ 'message' => 'forbidden' ], 403 );
}
wp_send_json_success( [
'count' => (int) wp_count_posts()->publish,
] );
} );
JavaScriptにPHPの値を渡すときは、wp_add_inline_script() と wp_json_encode() を組み合わせると、<script> タグを自分で書かずに済みます。
wp_add_inline_script(
'my-script',
'const MY_DATA = ' . wp_json_encode( [ 'restUrl' => esc_url_raw( rest_url( 'my/v1/' ) ) ] ) . ';',
'before'
);
コードはテーマを直接編集せず、子テーマかプラグインに追加し、ステージング環境で動作を確認してから本番に反映します。PHPのバージョンによって使える関数やフラグが異なります(2026年10月現在)。たとえば json_validate() はPHP 8.3以降でしか使えないため、サーバーのPHPバージョンも確認してください。
まとめ
json_encode()はJSON_UNESCAPED_UNICODEなどのフラグで出力を調整する。HTMLに埋め込むならJSON_HEX_TAGなどを付ける- キーが連番でない配列はオブジェクトになる。配列で返したいときは
array_values()で振り直す json_decode()は第2引数trueで連想配列に。失敗もnullになるため、JSON_THROW_ON_ERRORかjson_last_error()で検出する- APIでは
php://inputから本文を読み、中身の型を確認してから使う。応答にはContent-Typeを付け、余計な出力を混ぜない - WordPressでは
wp_json_encode()、wp_send_json_success()/wp_send_json_error()、REST APIのレスポンスを使い、nonceと権限の確認を省かない