登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  php教程

PHP json_decode JSON_THROW_ON_ERROR 怎样统一处理解析失败

来源:17golang原创

时间:2026-10-26 22:57:28 312浏览 收藏

给 json_decode() 的第四个参数传入 JSON_THROW_ON_ERROR,再在统一输入边界捕获 JsonException,就能把语法、编码和嵌套深度等解析失败集中处理。合法 JSON null 仍会正常返回 PHP 的 null,业务字段是否合格则需要另行判断。

官方手册:https://www.php.net/manual/en/function.json-decode.php。本文以箱件数量接口为例,示例使用 PHP 8.0+ 语法和行为,未运行代码。该标志本身从 PHP 7.3 起提供。

先保护输入结果的可信度

我会把这个问题拆成三种责任:入口控制数据规模,解析器确认 JSON 能否被解码,业务层确认结果是否符合接口契约。这样遇到失败时,能知道该修改发送内容、字段规则还是程序配置。

假设接口只接受一个包含整数 count 的对象,并要求数量介于 1 和 1000。这里要保护的是一条可用的业务记录,以及不会夹带原始报文的错误记录。风险既可能来自意外截断、错误编码,也可能来自不符合预期的大体积或深层输入;这些都不应被默认转换成一条“空记录”继续处理。

字节上限应在读取请求时就由服务器、框架或读取逻辑配合约束。本文函数里的长度判断只能限制已经读入的字符串,不能撤销此前发生的读取和内存占用。

输入配额、JSON 解析及业务对象的静态责任边界图
静态责任说明图:三个分组分别约束输入规模、JSON 结构与业务字段,不代表执行截图。

把解析失败集中到 JsonException

旧代码如果只判断返回值是否为 null,会混淆两种情况:输入是合法的 JSON null,或者输入根本解析失败。启用异常后,解析失败进入 catch,合法值仍走正常返回路径;不要再把 json_last_error() 当作这次异常模式调用的主要错误来源。

JSON 常量文档明确说明,该标志使用异常替代原先通过全局错误状态读取失败的方式。错误类型可以从 $error->getCode() 取得,说明文字则来自异常消息。统一处理应依赖明确错误分类,而不是匹配一段可能变化的英文报错文本。

下面的函数故意不在内部吞掉解析异常,让入口调用者决定如何返回错误。

 65536) {
        throw new LengthException('输入超过接口允许的字节数');
    }

    // 对象保留为 stdClass,便于区分 JSON 对象与数组
    // 32 是示例允许的嵌套深度,按实际接口契约配置
    $value = json_decode($raw, false, 32, JSON_THROW_ON_ERROR);

    if (!$value instanceof stdClass) {
        throw new UnexpectedValueException('顶层必须是 JSON 对象');
    }
    // 类型和范围属于业务约束,解析器不会替我们确认
    if (!property_exists($value, 'count')
        || !is_int($value->count)
        || $value->count count > 1000) {
        throw new UnexpectedValueException('count 必须是 1 到 1000 的整数');
    }

    // 仅输出业务需要的字段,不直接透传任意输入属性
    return ['count' => $value->count];
}

解析成功后继续检查业务结构

JSON 能被解析,并不代表输入就是接口需要的对象。null、布尔值、字符串、数字和数组都可能是合法 JSON。示例选择 associative=false,并要求返回值是 stdClass,从而把顶层对象与数组区分开。

接下来检查 count 是否存在、是否为整数以及范围是否合格。字符串 "12" 与整数 12 在此契约中不同;是否允许字符串转换,应由业务明确决定,不能依赖隐式转换恰好成功。

这个函数只返回 count 字段,其他输入属性没有进入返回记录。它没有实现完整 JSON Schema,也不承担账号权限、业务幂等或数据库约束;需要这些控制时,应放到对应业务层继续处理。

对我来说,提前分清这些责任比把所有异常包装成同一句“JSON 格式错误”更有用:调用方不会为了修复字段范围问题,反复调整一份语法本来正确的 JSON。

为失败分支保留最少审计信息

 true, 'data' => $parcel];
} catch (JsonException $error) {
    // 不写入原始报文,只记录请求标识和解析错误码
    error_log(sprintf(
        'request_id=%s stage=json_decode json_code=%d',
        $requestId,
        $error->getCode()
    ));
    $result = ['ok' => false, 'error' => 'invalid_json'];
} catch (LengthException $error) {
    // 超配额是入口拒绝,不伪装成 JSON 语法异常
    $result = ['ok' => false, 'error' => 'payload_too_large'];
} catch (UnexpectedValueException $error) {
    // 业务结构错误使用独立错误分类
    $result = ['ok' => false, 'error' => 'invalid_payload'];
}

示例把三类预期失败映射成三个稳定标识:invalid_json、payload_too_large 和 invalid_payload。这只是应用层结果对象,不是完整 HTTP 控制器;接入框架时,再按接口协议设置响应状态、响应头和序列化方式。

审计记录只展示固定请求标识、阶段和解析错误码。生产环境的请求标识应由服务端生成或经过规范化,日志访问范围与保留期限由项目管理。不要为方便排查就把整段请求体、令牌或个人数据拼进日志,也不要直接向外部调用者回传异常堆栈。

解析异常、输入超限和业务字段错误的静态分类关系图
静态分类说明图:预期输入错误各有归属,程序配置错误不混入用户解析失败。

这里没有捕获所有 Throwable 并一律返回 invalid_json。比如 PHP 8 中把 depth 配成不在允许范围内的值,会触发 ValueError;这属于调用参数错误,与报文的嵌套超过一个合法深度上限不同。上层统一异常处理器仍要承担意外程序错误的记录与响应。

按输入类型确认处理边界

下表是从代码和手册推导的处理预期,用来说明输入契约,未声称已运行这些案例。

输入情形应归属的位置示例结果分类
对象含 count 整数 12解析与字段检查均满足ok 为 true
合法 JSON null业务顶层对象要求invalid_payload
对象末尾多一个逗号JSON 解析invalid_json
count 是字符串或超范围业务字段规则invalid_payload
字符串长度超过示例配额入口字节配额payload_too_large
报文超过合法设置的解析深度JSON 解析invalid_json

保持深度限制与实际结构相称,也不要为了让编码错误消失就随意加入 JSON_INVALID_UTF8_IGNORE 或 JSON_INVALID_UTF8_SUBSTITUTE。这些选项会改变无效 UTF-8 的处理方式;是否接受丢弃或替换字符,应由字段含义与接口契约决定。

启用异常后,null 就一定是错误吗?

不是。JSON null 是合法值;示例拒绝它是因为本接口要求对象,而不是解析器失败。

捕获 JsonException 能保证输入安全吗?

不能。它只是解析失败通道,不能替代请求大小控制、字段规则、权限或业务约束。

是否需要先解析一次检查,再解析一次取值?

本例只解码一次,再对解码值做结构判断。这样解析结果与业务检查针对的是同一个值。

依据:json_decode 手册、JSON 预定义常量与JsonException 类说明。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>