Skip to content

JSON 处理

JSON(JavaScript Object Notation)是现代 PHP API 最常用的数据交换格式。PHP 提供了 json_encode()json_decode()json_validate() 等函数来处理 JSON 数据。本节深入讲解 JSON 编码/解码的各种选项、错误处理、安全注意事项和性能优化。

前置知识

阅读本节前,建议先了解:RESTful API 设计PHP 类型系统

基础概念

JSON 数据类型映射

JSON 类型PHP 类型示例
objectstdClassarray{"name":"Alice"}
arrayarray(索引数组)[1,2,3]
stringstring"hello"
number (integer)int42
number (float)float3.14
booleanbooltrue / false
nullnullnull

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 认证

参考链接