当前位置:首页 > 文章列表 > 文章 > php教程 > PHP JsonSerializable 控制对象输出字段

PHP JsonSerializable 控制对象输出字段

来源:17golang原创 2026-10-01 21:11:42 0浏览 收藏

JsonSerializable 的作用,是让对象在传给 json_encode() 时主动声明“哪些字段属于 JSON 输出契约”。默认情况下,PHP 对普通对象只编码公开属性;实现该接口后,可以从私有属性中挑选字段、改名、组合派生值,并排除密码摘要、内部状态等不应暴露的数据。

最小做法是让类实现 JsonSerializable,然后在 jsonSerialize(): mixed 中返回一个可被 json_encode() 原生编码的值。API 场景通常返回关联数组,并在最外层编码时加入 JSON_THROW_ON_ERROR,让非法 UTF-8、递归引用或不支持的类型以异常形式进入统一错误处理。

官方地址:https://www.php.net/manual/en/class.jsonserializable.php

要点速览
  • jsonSerialize() 决定对象的 JSON 表示,不改变对象本身的属性可见性。
  • 方法可以返回数组、标量、对象或 null,但不能返回 resource;API 通常用关联数组保持字段结构明确。
  • 敏感字段应采用“允许列表”显式输出,不要先导出全部属性再删除。

先用最小写法固定输出字段

下面的 UserProfile 保存了数据库主键、显示名称、邮箱、密码摘要和启用状态,但 JSON 只需要面向调用方提供稳定的公开表示。私有属性仍保持封装,jsonSerialize() 只返回允许暴露的字段。

 $this->id,
            'display_name' => $this->displayName,
            'email' => $this->email,
            'active' => $this->active,
        ];
    }
}

$profile = new UserProfile(
    42,
    '林海',
    'linhai@example.test',
    '$2y$10$internal-only',
    true,
);

// 统一在响应边界编码,失败时抛出 JsonException。
$json = json_encode(
    $profile,
    JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR,
);

echo $json;

结果结构包含 id、display_name、email 和 active。passwordHash 没有出现在返回数组里,因此不会被编码。这里不是利用 private 自动隐藏敏感值,而是通过允许列表把接口契约写死;即使以后属性改成 public,也不会意外扩大响应。

把对象模型与 API 字段契约分开

领域对象的字段名服务于内部代码,JSON 字段名服务于外部调用方,两者不必完全一致。比如 PHP 属性使用 $displayName,API 可以稳定输出 display_name;对象内部保存 DateTimeImmutable,API 则输出带时区的文本。这样数据库重构、属性可见性调整或内部类型替换,不必直接破坏客户端。

静态关系上,领域对象保留 私有属性 与 passwordHash,jsonSerialize() 只组装 公开字段数组,最后由 json_encode() 生成 JSON 响应。敏感字段不属于输出契约,应该在最初的字段选择阶段被排除。

领域对象、私有属性、passwordHash、jsonSerialize、公开字段数组、json_encode 与 JSON 响应的静态输出契约图
图1:JsonSerializable 输出字段契约结构图;重点看内部属性与公开字段数组之间的边界,图中连线只表示静态依赖。
设计项推荐做法原因
字段选择显式返回允许列表新增内部属性时不会自动泄露
字段命名以 API 契约为准避免客户端依赖 PHP 内部命名
日期时间格式化为约定文本调用方无需理解 PHP 对象结构
错误处理外层使用 JSON_THROW_ON_ERROR避免把 false 当成合法响应

处理别名、派生字段和可空值

jsonSerialize() 不必逐字复制对象属性。它可以添加派生字段、重命名字段,也可以根据契约决定 null 是保留还是省略。关键是行为要稳定:同一个资源的同一个接口,不应因为某次属性为空就随意改变数据类型。

 $this->number,
            'amount' => [
                'value' => $this->amountCents,
                'unit' => 'cent',
                'currency' => $this->currency,
            ],
            // 未支付时明确返回 null,客户端字段形状保持稳定。
            'paid_at' => $this->paidAt?->format(DATE_ATOM),
        ];
    }
}

金额字段没有直接转换为小数,而是同时输出整数值、单位与币种,这属于接口设计决定。paid_at 在未支付时保留为 null,调用方可以区分“尚未支付”与“字段在当前版本不存在”。如果业务选择省略空值,也应集中在一个明确的响应映射层处理,而不是让不同类各自随意过滤。

嵌套对象不需要手动调用 jsonSerialize

当返回数组中还包含另一个实现 JsonSerializable 的对象时,可以直接把对象放入数组,交给 json_encode() 递归编码。不要在父对象中手动调用子对象的 jsonSerialize();手动调用会绕过统一编码边界,也让测试与错误处理更分散。

 $this->city,
            'street' => $this->street,
        ];
    }
}

final class CustomerView implements JsonSerializable
{
    public function __construct(
        private int $id,
        private AddressView $address,
    ) {
    }

    public function jsonSerialize(): mixed
    {
        return [
            'id' => $this->id,
            'address' => $this->address, // 由 json_encode 递归处理子对象。
        ];
    }
}

集合也遵循相同规则:返回 CustomerView[] 数组后,json_encode() 会逐个处理。需要注意循环引用,例如父对象返回子对象、子对象又返回父对象,会触发递归错误。输出 View 最好只保留单向、无环的响应结构。

把编码错误集中到响应边界

PHP 官方文档说明,jsonSerialize() 可以返回任何能被 json_encode() 原生处理的值,但 resource 不受支持;所有字符串还必须是有效 UTF-8。若沿用默认错误行为,json_encode() 失败会返回 false,调用方很容易遗漏检查。PHP 7.3 起可使用 JSON_THROW_ON_ERROR,失败时抛出 JsonException。

不要同时依赖 JSON_PARTIAL_OUTPUT_ON_ERROR 与 JSON_THROW_ON_ERROR 来获得严格失败,因为官方常量说明中,前者优先。对 API 来说,静默用 null 或 0 替换不可编码值可能掩盖数据问题;更稳妥的策略是让异常进入统一响应适配器,并返回不含内部细节的错误信息。

这套静态关系由 API 控制器 持有 根响应对象,根对象可包含 嵌套 View;json_encode() 使用 JSON_THROW_ON_ERROR,失败进入 JsonException,再由 响应适配器 统一转换。

API 控制器、根响应对象、嵌套 View、json_encode、JSON_THROW_ON_ERROR、JsonException 与响应适配器的静态错误边界图
图2:嵌套对象与 JSON 错误处理的静态关系图;编码选项和异常转换集中在响应边界,不分散到各个领域对象。

兼容性取舍要由调用方需求决定

实现 JsonSerializable 后,字段变化就是 API 变化。删除字段、改名或把字符串改成对象,都会影响客户端;新增可选字段通常更容易兼容,但仍要考虑严格 schema 校验。大型项目可以让领域对象保持纯净,再建立专门的 UserView、InvoiceView 或响应 DTO 实现接口,以免同一个领域对象被多个接口迫使承担不同 JSON 形状。

  • 同一对象只有一种公开表示:直接在对象上实现 JsonSerializable 较简洁。
  • 不同接口需要不同字段:建立独立 View/DTO,避免用全局状态或当前用户身份改变 jsonSerialize() 结果。
  • 需要字段版本:在响应构造层选择 V1/V2 View,不要让一个方法根据隐式环境返回两套形状。
  • 支持较旧 PHP:mixed 返回类型需要 PHP 8.0;面向更早版本时应按目标运行时调整声明并做兼容测试。

JsonSerializable 只控制 json_encode() 的 JSON 表示,它与 PHP 的 Serializable、__serialize() 和 serialize() 不是同一套机制。前者服务跨语言 JSON 契约,后者服务 PHP 值的可存储表示,不能互相替代。

完整示例:返回稳定的用户详情响应

下面把字段允许列表、日期格式、嵌套对象和异常编码组合到一起。控制器只负责创建响应对象并调用统一编码器,具体 JSON 字段由 View 自己声明。

 $this->code,
            'label' => $this->label,
        ];
    }
}

final class UserDetailView implements JsonSerializable
{
    /** @param list $roles */
    public function __construct(
        private int $id,
        private string $displayName,
        private DateTimeImmutable $createdAt,
        private array $roles,
        private string $internalNote,
    ) {
    }

    public function jsonSerialize(): mixed
    {
        // internalNote 是内部字段,故意不进入公开响应。
        return [
            'id' => $this->id,
            'display_name' => $this->displayName,
            'created_at' => $this->createdAt->format(DATE_ATOM),
            'roles' => $this->roles,
        ];
    }
}

$response = new UserDetailView(
    42,
    '林海',
    new DateTimeImmutable('2026-09-01T08:30:00+08:00'),
    [new RoleView('editor', '内容编辑')],
    '仅供内部审核使用',
);

try {
    // HTTP 层可把这里的字符串写入响应体,并设置 application/json。
    echo json_encode(
        $response,
        JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR,
    );
} catch (JsonException $error) {
    // 实际项目应交给统一异常处理中间件生成错误响应。
    http_response_code(500);
    echo '{"error":"响应编码失败"}';
}

检查这段设计时,只需确认四件事:内部字段没有进入允许列表;字段名称符合 API 约定;嵌套对象也实现了清晰契约;编码错误不会以 false 混入正常响应。这样对象内部结构可以继续演进,而客户端看到的 JSON 保持稳定。

常见问题

jsonSerialize 必须返回数组吗?

不必须。官方签名返回 mixed,可以返回可被 json_encode() 编码的数组、对象、标量或 null,但不能返回 resource。API 对象通常返回关联数组,因为字段语义最清楚。

private 属性会自动进入 JSON 吗?

普通对象默认只编码公开属性。实现 JsonSerializable 后,最终输出由 jsonSerialize() 的返回值决定,因此私有属性只有在方法显式放入返回值时才会出现。

可以在 jsonSerialize 中根据当前登录用户隐藏字段吗?

技术上可以读取外部状态,但不推荐。序列化结果会变得难以预测和测试。更清楚的方式是在响应构造层选择不同的 View 或字段集合,再让每个 View 保持确定输出。

为什么推荐 JSON_THROW_ON_ERROR?

它会在编码失败时抛出 JsonException,便于统一错误处理,避免遗漏 json_encode() 返回 false 的分支。需要严格失败时,不要再混用优先级更高的 JSON_PARTIAL_OUTPUT_ON_ERROR。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
photocolors地理位置钢印怎么关?EXIF读取、手动修改与隐私边界说明photocolors地理位置钢印怎么关?EXIF读取、手动修改与隐私边界说明
上一篇
photocolors地理位置钢印怎么关?EXIF读取、手动修改与隐私边界说明
Go sync/atomic Uint64 对齐与无锁计数方案
下一篇
Go sync/atomic Uint64 对齐与无锁计数方案
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    290次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    342次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    344次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    308次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    130次使用