Skip to content

JSON 编解码

JSON(JavaScript Object Notation)是现代 Web 开发中最常用的数据交换格式。PHP 提供了 json_encode()json_decode() 两个核心函数用于 JSON 的编码和解码,PHP 8.2+ 还新增了 JsonSerializable 接口的改进。本节将全面讲解 PHP 的 JSON 处理。

前置知识

阅读本节前,建议先了解:PHP 类型系统数组操作

基础概念

JSON 格式

JSON 支持以下数据类型:

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

PHP 扩展

JSON 扩展是 PHP 默认启用的核心扩展(ext-json),不需要额外安装。

json_encode 编码

基本用法

php
<?php
declare(strict_types=1);

// 编码数组
$data = [
    'name'  => '张三',
    'age'   => 30,
    'email' => 'zhangsan@example.com',
    'active' => true,
    'scores' => [95, 88, 92, 78],
];

$json = json_encode($data);
// {"name":"张三","age":30,"email":"zhangsan@example.com","active":true,"scores":[95,88,92,78]}

// 格式化输出(美化)
$json = json_encode($data, JSON_PRETTY_PRINT);

编码选项

php
<?php
declare(strict_types=1);

$data = [
    'name'      => '张三',
    'content'   => '<strong>HTML 内容</strong>',
    'path'      => 'C:\\Users\\test',
    'unicode'   => '中文 Japanese 한국어',
    'empty'     => '',
    'number'    => 123456.789,
    'bigNum'    => 999999999999999999,
];

// JSON_UNESCAPED_UNICODE - 不转义 Unicode 字符
echo json_encode($data, JSON_UNESCAPED_UNICODE);
// {"name":"张三","content":"<strong>HTML 内容</strong>"...}

// JSON_UNESCAPED_SLASHES - 不转义斜杠
echo json_encode($data, JSON_UNESCAPED_SLASHES);

// JSON_PRETTY_PRINT - 格式化输出
echo json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);

// JSON_FORCE_OBJECT - 强制将数组编码为对象
echo json_encode([1, 2, 3], JSON_FORCE_OBJECT);
// {"0":1,"1":2,"2":3}

// JSON_HEX_TAG / JSON_HEX_AMP / JSON_HEX_APOS / JSON_HEX_QUOT
// 将特殊字符编码为十六进制
echo json_encode($data, JSON_HEX_TAG | JSON_HEX_AMP);

// JSON_NUMERIC_CHECK - 将数字字符串转为数字
echo json_encode(['id' => '123', 'price' => '99.99'], JSON_NUMERIC_CHECK);

// JSON_PRESERVE_ZERO_FRACTION - 保留浮点数的零小数
echo json_encode(10.0, JSON_PRESERVE_ZERO_FRACTION);
// 10.0 (而不是 10)

// JSON_THROW_ON_ERROR - 错误时抛出 JsonException(PHP 7.3+)
try {
    $json = json_encode("\xB1\x22", JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
    echo "JSON 编码错误: " . $e->getMessage();
}

// JSON_INVALID_UTF8_SUBSTITUTE (PHP 7.2+)
// 无效 UTF-8 替换为 Unicode 替换字符
echo json_encode("\xFF", JSON_INVALID_UTF8_SUBSTITUTE);

// JSON_INVALID_UTF8_IGNORE (PHP 7.2+)
// 忽略无效 UTF-8 字符
echo json_encode("\xFF text", JSON_INVALID_UTF8_IGNORE);

// JSON_PARTIAL_OUTPUT_ON_ERROR - 部分输出(即使出错也返回部分结果)
echo json_encode(["depth" => "too"."\x80"."\ndeep"], JSON_PARTIAL_OUTPUT_ON_ERROR);

// JSON_UNESCAPED_LINE_TERMINATORS / JSON_UNESCAPED_UNICODE (PHP 7.1+)
// 常用组合
$prettyFlags = JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES;
echo json_encode($data, $prettyFlags);

编码深度限制

php
<?php
declare(strict_types=1);

// 深层嵌套数据
$deepData = [];
$current = &$deepData;
for ($i = 0; $i < 520; $i++) {
    $current['level'] = $i;
    $current['child'] = [];
    $current = &$current['child'];
}

// PHP 8.1+ 默认深度限制为 512
// json_encode($deepData); // 返回 false

// 设置更大的深度(PHP 8.1+ 通过 json_encode 的 options)
$json = json_encode($deepData, 0, 1024); // depth 参数

json_decode 解码

基本用法

php
<?php
declare(strict_types=1);

$json = '{"name":"张三","age":30,"active":true,"scores":[95,88,92]}';

// 解码为关联数组
$data = json_decode($json, true);
// ['name' => '张三', 'age' => 30, 'active' => true, 'scores' => [95, 88, 92]]

// 解码为对象(默认)
$obj = json_decode($json);
// stdClass { name: '张三', age: 30, active: true, scores: [95, 88, 92] }

// 访问对象属性
echo $obj->name;    // 张三
echo $obj->scores[0]; // 95

解码选项

php
<?php
declare(strict_types=1);

$json = '{"name":"张三","empty":"","null_value":null,"big_number":12345678901234567890}';

// JSON_BIGINT_AS_STRING - 大整数转为字符串
$data = json_decode($json, true, 512, JSON_BIGINT_AS_STRING);
// 'big_number' => '12345678901234567890'

// JSON_OBJECT_AS_ARRAY - 强制对象为数组
$data = json_decode($json, false, 512, JSON_OBJECT_AS_ARRAY);

// JSON_THROW_ON_ERROR - 错误时抛出异常
try {
    $data = json_decode('{"invalid": }', true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
    echo "JSON 解码错误: " . $e->getMessage();
}

// JSON_INVALID_UTF8_SUBSTITUTE / JSON_INVALID_UTF8_IGNORE (PHP 7.2+)
// 处理无效 UTF-8
$data = json_decode($jsonWithBadUtf8, true, 512, JSON_INVALID_UTF8_SUBSTITUTE);

错误处理

php
<?php
declare(strict_types=1);

// 方式一:JSON_THROW_ON_ERROR
try {
    $data = json_decode('{invalid}', true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
    echo "错误: " . $e->getMessage() . PHP_EOL;
}

// 方式二:手动检查
$json = '{invalid json}';
$data = json_decode($json);

if ($data === null && json_last_error() !== JSON_ERROR_NONE) {
    echo "错误: " . json_last_error_msg() . PHP_EOL;
}

// json_last_error() 返回错误码
// json_last_error_msg() 返回错误消息(PHP 5.5+)

// 错误码常量:
// JSON_ERROR_NONE           - 无错误
// JSON_ERROR_DEPTH          - 超过最大深度
// JSON_ERROR_STATE_MISMATCH - 状态不匹配
// JSON_ERROR_CTRL_CHAR      - 遇到意外的控制字符
// JSON_ERROR_SYNTAX         - 语法错误
// JSON_ERROR_UTF8           - UTF-8 编码错误
// JSON_ERROR_RECURSION      - 递归引用
// JSON_ERROR_INF_OR_NAN     - INF 或 NaN
// JSON_ERROR_UNSUPPORTED_TYPE - 不支持的类型
// JSON_ERROR_INVALID_PROPERTY_NAME (PHP 7.0+)
// JSON_ERROR_NON_BACKED_ENUM_TYPE (PHP 8.1+)

JsonSerializable 接口

基本实现

php
<?php
declare(strict_types=1);

class User implements JsonSerializable
{
    public function __construct(
        private readonly int $id,
        private readonly string $name,
        private readonly string $email,
        private readonly \DateTime $createdAt,
        private readonly array $roles = []
    ) {}

    public function jsonSerialize(): mixed
    {
        return [
            'id'         => $this->id,
            'name'       => $this->name,
            'email'      => $this->email,
            'created_at' => $this->createdAt->format('c'),
            'roles'      => $this->roles,
        ];
    }
}

$user = new User(
    1,
    '张三',
    'zhangsan@example.com',
    new \DateTime('2024-01-15'),
    ['admin', 'editor']
);

echo json_encode($user, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
// {
//     "id": 1,
//     "name": "张三",
//     "email": "zhangsan@example.com",
//     "created_at": "2024-01-15T00:00:00+00:00",
//     "roles": ["admin", "editor"]
// }

JsonSerializable 嵌套对象

php
<?php
declare(strict_types=1);

class Address implements JsonSerializable
{
    public function __construct(
        private readonly string $street,
        private readonly string $city,
        private readonly string $zipCode
    ) {}

    public function jsonSerialize(): mixed
    {
        return [
            'street'  => $this->street,
            'city'    => $this->city,
            'zipCode' => $this->zipCode,
        ];
    }
}

class Order implements JsonSerializable
{
    public function __construct(
        private readonly string $orderId,
        private readonly User $user,
        private readonly Address $address,
        private readonly array $items = []
    ) {}

    public function jsonSerialize(): mixed
    {
        return [
            'order_id' => $this->orderId,
            'user'     => $this->user,       // 自动调用 User::jsonSerialize()
            'address'  => $this->address,    // 自动调用 Address::jsonSerialize()
            'items'    => $this->items,
            'total'    => array_sum(array_column($this->items, 'price')),
        ];
    }
}

PHP 8.1+ 枚举与 JSON

php
<?php
declare(strict_types=1);

// PHP 8.1+ Backed Enum 自动支持 JSON 编解码
enum Status: string implements JsonSerializable
{
    case Active = 'active';
    case Inactive = 'inactive';
    case Pending = 'pending';

    public function jsonSerialize(): string
    {
        return $this->value;
    }
}

enum Color: int
{
    case Red = 1;
    case Green = 2;
    case Blue = 3;
}

// Backed Enum 可以直接 json_encode
echo json_encode(Status::Active); // "active"
echo json_encode(Color::Red);     // 1

// 解码需要手动映射
$json = '"active"';
$statusValue = json_decode($json, true);
$status = Status::tryFrom($statusValue);

实战示例

JSON API 响应

php
<?php
declare(strict_types=1);

/**
 * JSON API 响应构造器
 */
class JsonResponse
{
    public static function success(
        mixed $data = null,
        string $message = 'success',
        int $code = 0
    ): string {
        return json_encode([
            'code'    => $code,
            'message' => $message,
            'data'    => $data,
        ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    }

    public static function error(
        string $message,
        int $code = 500,
        mixed $data = null
    ): string {
        return json_encode([
            'code'    => $code,
            'message' => $message,
            'data'    => $data,
        ], JSON_UNESCAPED_UNICODE);
    }

    public static function paginated(
        array $items,
        int $total,
        int $page,
        int $perPage
    ): string {
        return json_encode([
            'code'    => 0,
            'data'    => $items,
            'pagination' => [
                'total'     => $total,
                'page'      => $page,
                'per_page'  => $perPage,
                'last_page' => (int) ceil($total / $perPage),
            ],
        ], JSON_UNESCAPED_UNICODE);
    }
}

// 使用示例
echo JsonResponse::success(['user' => ['id' => 1, 'name' => '张三']]);
echo JsonResponse::error('参数无效', 400);
echo JsonResponse::paginated($items, 100, 1, 10);

JSON 文件读写

php
<?php
declare(strict_types=1);

/**
 * JSON 文件操作
 */
class JsonFile
{
    public static function read(string $path, bool $associative = true): mixed
    {
        $content = file_get_contents($path);
        if ($content === false) {
            throw new RuntimeException("无法读取文件: {$path}");
        }

        $data = json_decode($content, $associative, 512, JSON_THROW_ON_ERROR);
        return $data;
    }

    public static function write(string $path, mixed $data, bool $pretty = true): void
    {
        $flags = JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES;
        if ($pretty) {
            $flags |= JSON_PRETTY_PRINT;
        }

        $json = json_encode($data, $flags);

        if ($json === false) {
            throw new RuntimeException("JSON 编码失败");
        }

        $result = file_put_contents($path, $json);
        if ($result === false) {
            throw new RuntimeException("无法写入文件: {$path}");
        }
    }

    public static function merge(string $path, array $data): void
    {
        $existing = self::read($path);
        $merged = array_merge($existing, $data);
        self::write($path, $merged);
    }
}

// 使用示例
// JsonFile::write('config.json', ['debug' => true, 'cache' => false]);
// $config = JsonFile::read('config.json');
// JsonFile::merge('config.json', ['timezone' => 'Asia/Shanghai']);

注意事项

编码陷阱

php
<?php
declare(strict_types=1);

// 1. 索引数组和关联数组的区别
$indexed = ['a', 'b', 'c'];          // JSON 数组
$assoc = ['x' => 'a', 'y' => 'b'];  // JSON 对象
$mixed = [0 => 'a', 'x' => 'b'];    // JSON 对象(包含非连续/非数字键)

// 2. json_decode 返回 null
$json = 'null';
$data = json_decode($json, true); // null

// 无法区分 "null" 和解码失败,需要检查 json_last_error()
if ($data === null && json_last_error() !== JSON_ERROR_NONE) {
    // 解码失败
}

// 3. 浮点精度
echo json_encode(0.1 + 0.2); // 0.30000000000000004
echo json_encode(round(0.1 + 0.2, 2)); // 0.3

// 4. 递归引用
$a = [];
$a[] = &$a;
json_encode($a); // JSON_ERROR_RECURSION

最佳实践

  1. 始终使用 JSON_THROW_ON_ERROR:避免静默失败
  2. 使用 JSON_UNESCAPED_UNICODE:中文等 Unicode 字符直接输出
  3. 实现 JsonSerializable:自定义对象的 JSON 序列化
  4. 处理大整数:使用 JSON_BIGINT_AS_STRING 防止精度丢失
  5. 验证 JSON 输入:使用 json_validate()(PHP 8.3+)

下一节

继续学习:json_validate

参考链接