JSON 处理
JSON(JavaScript Object Notation)是现代 PHP API 最常用的数据交换格式。PHP 提供了 json_encode()、json_decode() 和 json_validate() 等函数来处理 JSON 数据。本节深入讲解 JSON 编码/解码的各种选项、错误处理、安全注意事项和性能优化。
前置知识
阅读本节前,建议先了解:RESTful API 设计、PHP 类型系统
基础概念
JSON 数据类型映射
| JSON 类型 | PHP 类型 | 示例 |
|---|---|---|
| object | stdClass 或 array | {"name":"Alice"} |
| array | array(索引数组) | [1,2,3] |
| string | string | "hello" |
| number (integer) | int | 42 |
| number (float) | float | 3.14 |
| boolean | bool | true / false |
| null | null | null |
json_encode()
函数签名与参数
php
<?php
declare(strict_types=1);
// json_encode(
// mixed $value,
// int $flags = 0,
// int $depth = 512
// ): string|false基本用法
php
<?php
declare(strict_types=1);
// 编码数组
$data = ['name' => 'Alice', 'age' => 30, 'active' => true];
echo json_encode($data);
// {"name":"Alice","age":30,"active":true}
// 编码索引数组
$list = [1, 2, 3, 'hello'];
echo json_encode($list);
// [1,2,3,"hello"]
// 编码对象
class User
{
public function __construct(
public readonly int $id,
public readonly string $name,
) {}
}
$user = new User(1, 'Alice');
echo json_encode($user);
// {"id":1,"name":"Alice"}(公开属性)编码标志(flags)
php
<?php
declare(strict_types=1);
$data = ['name' => '中文测试', 'url' => 'https://example.com/path', 'script' => '<script>'];
// JSON_HEX_TAG -- 编码 < 和 >
echo json_encode($data, JSON_HEX_TAG);
// {"name":"\u4e2d\u6587\u6d4b\u8bd5","url":"https:\/\/example.com\/path","script":"\u003Cscript\u003E"}
// JSON_HEX_AMP -- 编码 &
echo json_encode(['a & b'], JSON_HEX_AMP);
// {"a \u0026 b"}
// JSON_HEX_APOS -- 编码 '
echo json_encode(["it's"], JSON_HEX_APOS);
// ["it\u0027s"]
// JSON_HEX_QUOT -- 编码 "
echo json_encode(['he said "hi"'], JSON_HEX_QUOT);
// ["he said \u0022hi\u0022"]
// JSON_UNESCAPED_UNICODE -- 不转义 Unicode 字符(推荐)
echo json_encode(['name' => '中文测试'], JSON_UNESCAPED_UNICODE);
// {"name":"中文测试"}
// JSON_UNESCAPED_SLASHES -- 不转义斜杠
echo json_encode(['url' => 'https://example.com/path'], JSON_UNESCAPED_SLASHES);
// {"url":"https://example.com/path"}
// JSON_PRETTY_PRINT -- 格式化输出(调试用)
echo json_encode(['users' => [['id' => 1], ['id' => 2]]], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
// {
// "users": [
// {"id": 1},
// {"id": 2}
// ]
// }
// JSON_THROW_ON_ERROR -- 错误时抛出 JsonException(PHP 7.3+,推荐)
try {
echo json_encode(INF, JSON_THROW_ON_ERROR); // Inf and NaN cannot be JSON encoded
} catch (JsonException $e) {
echo "JSON Error: " . $e->getMessage();
}
// 常用组合标志
$flags = JSON_UNESCAPED_UNICODE // 中文不转义
| JSON_UNESCAPED_SLASHES // 斜杠不转义
| JSON_THROW_ON_ERROR; // 错误抛异常
echo json_encode($data, $flags);处理特殊值
php
<?php
declare(strict_types=1);
// NaN 和 Inf 不能直接编码为 JSON
// json_encode(NaN) 返回 false
// 解决方案一:JSON_PARTIAL_OUTPUT_ON_ERROR
echo json_encode(['valid' => 1, 'invalid' => NAN], JSON_PARTIAL_OUTPUT_ON_ERROR);
// {"valid":1,"invalid":null}
// 解决方案二:使用 JSON_INVALID_UTF8_SUBSTITUTE
$badString = "\xFF\xFE invalid";
echo json_encode($badString, JSON_INVALID_UTF8_SUBSTITUTE);
// "\ufffd\ufffd invalid"
// 解决方案三:自定义序列化
function safeJsonEncode(mixed $data): string
{
return json_encode($data, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
}
// 解决方案四:实现 JsonSerializable 接口
class SafeFloat implements JsonSerializable
{
public function __construct(
private readonly float $value,
) {}
public function jsonSerialize(): float|null
{
return is_nan($this->value) || is_infinite($this->value)
? null
: $this->value;
}
}depth 参数
php
<?php
declare(strict_types=1);
// depth 控制最大编码深度(默认 512)
$data = [];
for ($i = 0; $i < 600; $i++) {
$data = ['child' => $data];
}
echo json_encode($data); // false(深度超过限制)
echo json_encode($data, 0, 600); // 正常编码json_decode()
函数签名与参数
php
<?php
declare(strict_types=1);
// json_decode(
// string $json,
// ?bool $associative = null,
// int $depth = 512,
// int $flags = 0
// ): mixed基本用法
php
<?php
declare(strict_types=1);
$json = '{"name":"Alice","age":30,"active":true}';
// 解码为 stdClass 对象(默认)
$obj = json_decode($json);
echo $obj->name; // Alice
echo $obj->age; // 30
// 解码为关联数组(推荐)
$arr = json_decode($json, true);
echo $arr['name']; // Alice
echo $arr['age']; // 30
// 解码带标志
$data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
// 解码为指定类型(PHP 7.3+)
// flags:
// JSON_BIGINT_AS_STRING -- 大整数作为字符串返回
// JSON_OBJECT_AS_ARRAY -- 所有对象作为数组
// JSON_THROW_ON_ERROR -- 错误抛异常
$bigJson = '{"id": 9999999999999999999}';
$result = json_decode($bigJson, true, 512, JSON_BIGINT_AS_STRING);
echo gettype($result['id']); // string(避免精度丢失)解码验证
php
<?php
declare(strict_types=1);
// 验证 JSON 字符串是否有效
$json = '{"name":"Alice","age":30}';
// 方式一:json_decode 返回 null
$data = json_decode($json);
if ($data === null && json_last_error() !== JSON_ERROR_NONE) {
// JSON 解析失败
}
// 方式二:json_last_error()(推荐)
$data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
// 方式三:json_validate()(PHP 8.3+)
if (!json_validate($json)) {
echo "Invalid JSON";
}
// json_validate 不会实际解码数据,只验证语法
// 比 json_decode + json_last_error 更高效json_validate()
PHP 8.3+ 新函数
php
<?php
declare(strict_types=1);
/**
* json_validate(string $json, int $depth = 512): bool
* PHP 8.3+ 新增
* 仅验证 JSON 语法,不实际解码
* 比 json_decode(null) + json_last_error() 更高效
*/
$validJson = '{"name":"Alice","age":30}';
$invalidJson = '{"name":"Alice",}';
echo json_validate($validJson); // true
echo json_validate($invalidJson); // false
// 性能优势:不需要分配内存来存储解码结果
// 适用于先验证、后处理的场景
// 处理请求体时
$rawBody = file_get_contents('php://input');
if (!json_validate($rawBody)) {
http_response_code(400);
echo json_encode(['error' => 'Invalid JSON']);
exit;
}
// 验证通过后解码
$data = json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR);错误处理
json_last_error() 与 json_last_error_msg()
php
<?php
declare(strict_types=1);
$json = '{"invalid": "json}';
$data = json_decode($json);
if ($data === null) {
$errorCode = json_last_error();
$errorMsg = json_last_error_msg();
// 错误码映射
$errors = [
JSON_ERROR_NONE => 'No error',
JSON_ERROR_DEPTH => 'Maximum stack depth exceeded',
JSON_ERROR_STATE_MISMATCH => 'Underflow or modes mismatch',
JSON_ERROR_CTRL_CHAR => 'Unexpected control character found',
JSON_ERROR_SYNTAX => 'Syntax error',
JSON_ERROR_UTF8 => 'Malformed UTF-8 characters',
JSON_ERROR_RECURSION => 'Recursive references',
JSON_ERROR_INF_OR_NAN => 'Inf or NaN values',
JSON_ERROR_UNSUPPORTED_TYPE => 'Unsupported type',
JSON_ERROR_INVALID_PROPERTY_NAME => 'Invalid property name',
JSON_ERROR_UTF16 => 'Malformed UTF-16 characters',
];
$message = $errors[$errorCode] ?? 'Unknown error';
error_log("JSON Error [{$errorCode}]: {$message} - {$errorMsg}");
}
// PHP 7.3+ 推荐 JSON_THROW_ON_ERROR
try {
$data = json_decode('invalid json', true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
error_log("JSON Error: {$e->getMessage()}");
}实战示例:安全的 API JSON 处理
php
<?php
declare(strict_types=1);
class JsonHandler
{
private const FLAGS = JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES;
/**
* 安全编码
*/
public static function encode(mixed $data, bool $pretty = false): string
{
$flags = self::FLAGS | JSON_THROW_ON_ERROR;
if ($pretty) {
$flags |= JSON_PRETTY_PRINT;
}
return json_encode($data, $flags);
}
/**
* 安全解码
*/
public static function decode(string $json, bool $associative = true): mixed
{
// PHP 8.3+ 先验证
if (function_exists('json_validate')) {
if (!json_validate($json)) {
throw new InvalidArgumentException('Invalid JSON');
}
}
return json_decode($json, $associative, 512, JSON_THROW_ON_ERROR);
}
/**
* 解析请求体 JSON
*/
public static function parseBody(): array
{
$rawBody = file_get_contents('php://input');
$data = self::decode($rawBody);
if (!is_array($data)) {
throw new InvalidArgumentException('JSON must be an object or array');
}
return $data;
}
/**
* 输出 JSON 响应
*/
public static function respond(mixed $data, int $code = 200): never
{
http_response_code($code);
header('Content-Type: application/json; charset=utf-8');
echo self::encode($data);
exit;
}
/**
* 输出分页 JSON 响应
*/
public static function respondPaginated(
array $items,
int $total,
int $page,
int $perPage,
): never {
self::respond([
'data' => $items,
'meta' => [
'total' => $total,
'page' => $page,
'per_page' => $perPage,
'last_page' => (int) ceil($total / max(1, $perPage)),
],
]);
}
}
// === 使用示例 ===
// POST 请求处理
try {
$data = JsonHandler::parseBody();
// 验证 $data...
JsonHandler::respond(['success' => true, 'data' => $data], 201);
} catch (JsonException $e) {
JsonHandler::respond(['error' => 'Invalid JSON'], 400);
} catch (InvalidArgumentException $e) {
JsonHandler::respond(['error' => $e->getMessage()], 400);
}
// GET 列表响应
JsonHandler::respondPaginated(
items: [['id' => 1], ['id' => 2]],
total: 100,
page: 1,
perPage: 20,
);注意事项
1. JSON 最大嵌套深度
php
<?php
// 深度限制防止栈溢出攻击
// 默认 depth=512,对于大多数场景足够
// 过深的 JSON 可能导致递归解码时栈溢出
$data = json_decode($maliciousJson, true, 128); // 限制为 128 层2. 内存耗尽攻击
php
<?php
// 大型 JSON 请求体可能导致内存耗尽
$rawBody = file_get_contents('php://input');
$maxSize = 1024 * 1024; // 1MB
if (strlen($rawBody) > $maxSize) {
http_response_code(413);
echo json_encode(['error' => 'Request body too large']);
exit;
}最佳实践
1. 始终设置 JSON_THROW_ON_ERROR
php
<?php
// 推荐:统一异常处理
const JSON_FLAGS = JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR;
// 编码
echo json_encode($data, JSON_FLAGS);
// 解码
$data = json_decode($json, true, 512, JSON_FLAGS);
// 验证
if (PHP_VERSION_ID >= 80300) {
json_validate($json);
}下一节
继续学习:API 认证