json_validate(PHP 8.3+)
PHP 8.3 新增了 json_validate() 函数,用于高效地验证字符串是否为合法的 JSON 格式,而无需先解码整个字符串。这对于 API 入参校验、配置文件验证等场景非常有用。
前置知识
阅读本节前,建议先了解:JSON 编解码
基础概念
为什么需要 json_validate
在 PHP 8.3 之前,验证 JSON 字符串需要先调用 json_decode(),然后检查 json_last_error():
php
<?php
// 旧方式(PHP 8.3 之前)
$json = '{"name": "test"}';
$data = json_decode($json);
if ($data === null && json_last_error() !== JSON_ERROR_NONE) {
// JSON 无效
}这种方式的缺点:
- 需要分配内存解码整个 JSON
- 无法区分
null值和解码失败 - 代码不直观
PHP 8.3 的 json_validate() 解决了这些问题。
基本语法
函数签名
php
<?php
// json_validate(
// string $json,
// int $depth = 512,
// bool $associative = false
// ): bool
$json = '{"name": "张三", "age": 30}';
// 基本验证
$isValid = json_validate($json); // true
// 验证深度限制
$isValid = json_validate($json, 128);
// 返回 true 或 false使用示例
php
<?php
declare(strict_types=1);
// 有效的 JSON
$validJsons = [
'{"name":"张三","age":30}',
'null',
'true',
'false',
'42',
'"hello world"',
'[]',
'{}',
'[1,2,3]',
'{"nested":{"key":"value"}}',
'""',
'0',
'-1.5e10',
];
// 无效的 JSON
$invalidJsons = [
'{invalid}', // 缺少引号
'{"key": undefined}', // undefined 不是合法值
'{"key": "value"', // 缺少闭合括号
"{'key': 'value'}", // 单引号不合法
'{"a": [1, 2,]}', // 尾随逗号
"\xB1\x22", // 无效 UTF-8
'{"a":\x01}', // 控制字符
];
foreach ($validJsons as $json) {
echo "'{$json}' => " . (json_validate($json) ? 'valid' : 'invalid') . PHP_EOL;
}
foreach ($invalidJsons as $json) {
echo "'{$json}' => " . (json_validate($json) ? 'valid' : 'invalid') . PHP_EOL;
}详细说明
与 json_decode 的区别
php
<?php
declare(strict_types=1);
$json = 'null';
// json_decode 无法区分 null 值和错误
$data = json_decode($json);
if ($data === null && json_last_error() !== JSON_ERROR_NONE) {
// 这个条件为 false,因为 JSON 内容本身就是 null
}
// 必须额外检查 json_last_error()
// json_validate 直接返回结果
if (json_validate($json)) {
echo "合法 JSON" . PHP_EOL; // 输出
}
// 性能差异:json_validate 不分配解码内存
$largeJson = file_get_contents('large-data.json');
// 旧方式:需要解码 + 分配内存
$data = json_decode($largeJson);
if ($data === null && json_last_error() !== JSON_ERROR_NONE) {
// 无效
}
// 新方式:只验证语法,不解码
if (!json_validate($largeJson)) {
// 无效
}深度限制
php
<?php
declare(strict_types=1);
// 默认深度 512
$deepJson = json_encode(array_fill(0, 513, array_fill(0, 1, 'nested')));
// 超过深度限制
$isValid = json_validate($deepJson, 512); // false
echo "错误: " . json_last_error_msg() . PHP_EOL;
// JSON 错误: Maximum stack depth exceeded
// 增加深度限制
$isValid = json_validate($deepJson, 1024); // true
// depth 参数的作用与 json_decode 相同associative 参数
php
<?php
declare(strict_types=1);
// associative 参数(默认 false)
// 用于控制对象是解码为关联数组还是 stdClass
// 但 json_validate 只验证语法,此参数影响的是未来解码的行为预期
// 当 associative = false(默认),顶层对象视为 stdClass
$json = '{"name":"test"}';
json_validate($json, 512, false); // true
// 当 associative = true,顶层对象视为关联数组
json_validate($json, 512, true); // true
// 注意:两种情况下验证结果相同,因为只检查语法实战示例
API 入参验证
php
<?php
declare(strict_types=1);
/**
* API 请求 JSON 验证中间件
*/
class JsonRequestValidator
{
/**
* 验证请求体是否为合法 JSON
*/
public function validateRequestBody(string $body): void
{
if (empty(trim($body))) {
throw new InvalidArgumentException("请求体不能为空");
}
if (!json_validate($body)) {
throw new InvalidArgumentException(
'无效的 JSON 格式: ' . json_last_error_msg()
);
}
}
/**
* 验证并解码请求体
*/
public function validateAndDecode(string $body): array
{
$this->validateRequestBody($body);
$data = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
return $data;
}
/**
* 验证 JSON 是否匹配预期结构(简易版)
*/
public function validateStructure(
string $body,
array $requiredKeys = []
): array {
$data = $this->validateAndDecode($body);
foreach ($requiredKeys as $key) {
if (!array_key_exists($key, $data)) {
throw new InvalidArgumentException("缺少必填字段: {$key}");
}
}
return $data;
}
}
// 使用示例
$validator = new JsonRequestValidator();
try {
$body = '{"name":"张三","email":"zhangsan@example.com"}';
$data = $validator->validateStructure($body, ['name', 'email']);
echo "用户名: {$data['name']}" . PHP_EOL;
} catch (InvalidArgumentException $e) {
http_response_code(400);
echo json_encode(['error' => $e->getMessage()]);
}配置文件验证
php
<?php
declare(strict_types=1);
/**
* JSON 配置文件加载器
*/
class JsonConfigLoader
{
public function load(string $path): array
{
if (!file_exists($path)) {
throw new RuntimeException("配置文件不存在: {$path}");
}
$content = file_get_contents($path);
if ($content === false) {
throw new RuntimeException("无法读取配置文件: {$path}");
}
// 先验证 JSON 语法
if (!json_validate($content)) {
throw new RuntimeException(
"配置文件 JSON 格式无效: {$path} - " . json_last_error_msg()
);
}
// 再解码
$config = json_decode($content, true, 512, JSON_THROW_ON_ERROR);
return $config;
}
/**
* 热重载配置(先验证再应用)
*/
public function reloadIfValid(string $path, array &$currentConfig): bool
{
$content = file_get_contents($path);
if ($content === false) {
return false;
}
if (!json_validate($content)) {
return false; // 新配置无效,保持当前配置
}
$newConfig = json_decode($content, true, 512, JSON_THROW_ON_ERROR);
$currentConfig = $newConfig;
return true;
}
}
// 使用示例
$loader = new JsonConfigLoader();
try {
$config = $loader->load('/app/config/database.json');
echo "数据库: {$config['host']}" . PHP_EOL;
} catch (RuntimeException $e) {
echo "配置错误: " . $e->getMessage() . PHP_EOL;
}JSON 数据导入验证
php
<?php
declare(strict_types=1);
/**
* 批量 JSON 数据导入
*/
class JsonDataImporter
{
/**
* 逐行验证和导入 JSON Lines 格式
*/
public function importJsonLines(
string $filePath,
callable $onRecord,
int $maxErrors = 10
): array {
$results = [
'total' => 0,
'success' => 0,
'errors' => 0,
'errorLines' => [],
];
$file = fopen($filePath, 'r');
if ($file === false) {
throw new RuntimeException("无法打开文件: {$filePath}");
}
$lineNumber = 0;
while (($line = fgets($file)) !== false) {
$lineNumber++;
$line = trim($line);
if (empty($line)) {
continue;
}
$results['total']++;
if (!json_validate($line)) {
$results['errors']++;
$results['errorLines'][] = [
'line' => $lineNumber,
'error' => json_last_error_msg(),
'preview' => substr($line, 0, 100),
];
if ($results['errors'] >= $maxErrors) {
break;
}
continue;
}
$record = json_decode($line, true, 512, JSON_THROW_ON_ERROR);
$onRecord($record);
$results['success']++;
}
fclose($file);
return $results;
}
}
// 使用示例
// $importer = new JsonDataImporter();
// $results = $importer->importJsonLines('data.jsonl', function (array $record): void {
// // 处理每条记录
// echo "导入: " . json_encode($record) . PHP_EOL;
// });
// echo "成功: {$results['success']}, 失败: {$results['errors']}" . PHP_EOL;注意事项
兼容性
php
<?php
declare(strict_types=1);
// PHP 8.3+
if (function_exists('json_validate')) {
$isValid = json_validate($json);
} else {
// 向后兼容的 polyfill
$decoded = json_decode($json);
$isValid = json_last_error() === JSON_ERROR_NONE;
}性能比较
json_validate() 比 json_decode() + json_last_error() 更高效,因为:
- 不需要分配内存构建 PHP 数据结构
- 在发现第一个语法错误时可立即返回
- 对于大 JSON 字符串优势更明显
php
<?php
declare(strict_types=1);
// 性能测试(概念性代码)
$largeJson = file_get_contents('large.json');
// 旧方式
$start = microtime(true);
$data = json_decode($largeJson);
$valid = json_last_error() === JSON_ERROR_NONE;
$oldTime = microtime(true) - $start;
// 新方式 (PHP 8.3+)
$start = microtime(true);
$valid = json_validate($largeJson);
$newTime = microtime(true) - $start;
echo "旧方式: {$oldTime}s" . PHP_EOL;
echo "新方式: {$newTime}s" . PHP_EOL;最佳实践
- 优先使用
json_validate():PHP 8.3+ 验证 JSON 时首选 - 与
JSON_THROW_ON_ERROR配合:验证后解码使用异常模式 - 用于输入验证:API 请求、配置文件、用户上传的 JSON 数据
- 大文件先验证:避免解码巨大的无效 JSON 浪费资源
下一节
继续学习:Simdjson 高性能 JSON 解析