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

PHP 枚举序列化JSON 输出枚举值时为什么格式不一致怎样完成一个可复用的最小示例

来源:17golang原创

时间:2026-11-10 03:53:47 447浏览 收藏

接口里有一个状态字段时,PHP 枚举经常会出现“同样是 case,JSON 输出却不一样”的现象。原因不是 json_encode() 随机变化,而是纯枚举和 Backed Enum 的类型契约不同:纯枚举没有标量值,直接 JSON 序列化会报错;Backed Enum 默认输出它的 string 或 int 值。如果客户端还需要稳定拿到 case 名称,就应当显式实现 JsonSerializable。

官方手册:https://www.php.net/manual/en/language.enumerations.serialization.php

先确认接口要传“存储值”还是“可读名称”,再决定直接使用 Backed Enum 的默认输出,还是把 name 和 value 组装成固定对象。不要把 PHP 的内部 serialize() 结果当成对外 JSON 契约。

先分清两种枚举的默认输出

我排查这类问题时,会先看枚举声明有没有冒号和标量类型。只有 enum Status { ... } 的是纯枚举;写成 enum Status: string { ... } 或 enum Status: int { ... } 的是 Backed Enum。前者的 case 只有名称,后者还绑定了一个可用于传输或存储的标量值。

PHP 手册规定,Backed Enum 进入 json_encode() 时默认按其标量值表示;纯枚举直接进入 JSON 序列化则会抛出错误。两者都可以通过实现 JsonSerializable 改写这一默认行为。

Pure Enum 与 Backed Enum 经过 json_encode 后分别进入错误分支和标量值分支的静态关系说明图
图1:默认序列化结构说明图,查看纯枚举与 Backed Enum 的类型边界和 JSON 输出差异;不是运行截图。

用最小示例看见 JSON 输出边界

下面的示例故意把两个枚举放在同一个数组里,重点不是做完整接口,而是把默认契约摆在眼前。OrderState::Paid 是 Backed Enum,因此可以直接得到字符串值;Visibility::Public 是纯枚举,不能依赖默认 JSON 转换。

 OrderState::Paid,
    'visibility' => Visibility::Public,
];

// 对外接口不要把 serialize() 的内部格式当作 JSON 契约。
$json = json_encode($payload, JSON_THROW_ON_ERROR);
echo $json, PHP_EOL;

如果暂时移除 visibility,这个数组可以自然得到类似 {"state":"paid"} 的 JSON。加入纯枚举后,默认行为就不再是“自动输出 case 名称”,而是报错。这个差异解释了为什么某些接口只传出 paid,另一些接口却在序列化阶段失败。

用 JsonSerializable 固定接口对象

当客户端既要展示 Paid,又要提交稳定的 paid 时,最稳妥的做法不是让客户端猜测字段,而是让枚举明确返回对象。下面让 Backed Enum 自己实现 JsonSerializable,同时保留默认的 value 语义。

 $this->name,
            'value' => $this->value,
        ];
    }
}

// 外层业务对象保持普通数组,枚举负责自己的序列化契约。
$payload = ['status' => OrderStatus::Paid];
$json = json_encode($payload, JSON_THROW_ON_ERROR);
echo $json, PHP_EOL;

此时接口对象的形状可以稳定为 {"status":{"name":"Paid","value":"paid"}}。如果接口只需要数据库值,就不必为了展示名称而改变契约;可以在资源层显式使用 $status->value。如果还需要支持纯枚举,也可以让纯枚举实现同样的 JsonSerializable,返回只包含 name 的对象。

OrderStatus 的 name 和 value 通过 JsonSerializable 与 jsonSerialize 映射为稳定 API 对象的结构说明图
图2:自定义输出结构说明图,查看枚举名称和值如何汇聚到稳定的 API 对象;不是运行截图。

按接口契约选择字段并排查格式不一致

把选择落到项目里,可以按下面三种情况判断:

  • 数据库和接口都只认固定编码:使用 Backed Enum 的 value,保持字符串或整数类型不变。
  • 前端还要展示人类可读状态:实现 JsonSerializable,固定返回 name 与 value,不要让每个调用方自行拼装。
  • 枚举没有外部编码:不要假设纯枚举会自动变成名称;明确返回 $this->name 或一个带语义的对象。

遇到输出异常时,先检查枚举声明是否带 backed type,再查调用点是否把整个 case 传给 json_encode()。接着确认有没有实现 JsonSerializable,以及返回数组里的字段类型是否和旧客户端约定一致。最后区分 serialize() 与 json_encode():前者服务于 PHP 的序列化恢复,后者才是常见接口响应的 JSON 表示。

相关问题

为什么 Backed Enum 默认只输出 value?

因为 backed value 是枚举声明中明确绑定的标量,适合保持接口和存储字段的稳定类型。case 名称是否展示,应由接口契约显式决定。

纯枚举可以直接输出 case 名称吗?

不能依赖默认行为。纯枚举应实现 JsonSerializable,在 jsonSerialize() 中返回 $this->name 或结构化数组。

JsonSerializable 会影响 PHP 的 serialize() 吗?

它主要定义 JSON 序列化时返回的值,不等同于 PHP 内部的 serialize() 规则。两种序列化用途不同,应分别设计和测试。

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