
本文详解如何在 php 中深度比对两个结构相同的 json 对象(或关联数组),自动识别字段级差异,精确返回变更的键名、旧值与新值,适用于配置审计、数据同步、变更日志等场景。
本文详解如何在 php 中深度比对两个结构相同的 json 对象(或关联数组),自动识别字段级差异,精确返回变更的键名、旧值与新值,适用于配置审计、数据同步、变更日志等场景。
在实际开发中,我们常需对比前后两次获取的 JSON 数据(如 API 响应、数据库快照),快速定位哪些字段发生了变更。但直接使用 == 或 === 比较整个对象,仅能判断“是否相等”,无法提供差异明细;而简单遍历又易忽略类型安全、嵌套结构或空值边界问题。本文以你提供的示例为起点,提供一个健壮、可扩展、生产就绪的 PHP 差异提取方案。
✅ 核心思路:结构对齐 + 类型感知比对
你的原始代码已具备基本逻辑,但存在若干关键隐患:
- 未校验
$two是否为对象且含对应键($two->$key可能触发 Notice); - 使用
!=进行松散比较,对0 == "0"、null == ""等边界情况不严谨; - 仅支持单层对象(不处理嵌套数组/对象);
- 未兼容 JSON 字符串输入(常见于 API 场景)。
以下为优化后的完整实现:
<?php
/**
* 比较两个 JSON 对象(或关联数组),返回所有键值差异
* @param array|object|string $oldData 旧数据(JSON 字符串、数组或对象)
* @param array|object|string $newData 新数据(同上)
* @return array 差异列表,每个元素为 ['key_name'=>'xxx', 'old_value'=>..., 'new_value'=>...]
*/
function getJsonDifferences($oldData, $newData): array {
// 统一转为关联数组(确保键名可遍历)
$old = is_string($oldData) ? json_decode($oldData, true) : (array)$oldData;
$new = is_string($newData) ? json_decode($newData, true) : (array)$newData;
// 安全校验:确保两者均为非空数组
if (!is_array($old) || !is_array($new)) {
throw new InvalidArgumentException('Both inputs must be valid JSON strings or arrays');
}
$differences = [];
// 遍历旧对象所有键(兼顾新对象可能新增的键 —— 此处按题意只关注“变更”而非“新增”)
foreach ($old as $key => $oldValue) {
// 检查新对象是否存在该键
if (!array_key_exists($key, $new)) {
$differences[] = [
'key_name' => $key,
'old_value' => $oldValue,
'new_value' => null
];
continue;
}
$newValue = $new[$key];
// 严格比较(避免类型转换陷阱)
if ($oldValue !== $newValue) {
// 可选:对数组/对象做深度比较(此处简化为浅层,如需深度请扩展)
if (is_array($oldValue) && is_array($newValue)) {
// 若需递归比对嵌套结构,可调用自身或引入专用 diff 函数
$nestedDiff = getJsonDifferences($oldValue, $newValue);
if (!empty($nestedDiff)) {
// 将嵌套差异扁平化(带路径前缀),例如 "config.timeout"
foreach ($nestedDiff as $item) {
$item['key_name'] = $key . '.' . $item['key_name'];
$differences[] = $item;
}
}
continue;
}
$differences[] = [
'key_name' => $key,
'old_value' => $oldValue,
'new_value' => $newValue
];
}
}
// 可选:添加新对象独有键(若需“新增字段”也纳入结果)
// foreach (array_diff_key($new, $old) as $key => $value) {
// $differences[] = ['key_name'=>$key, 'old_value'=>null, 'new_value'=>$value];
// }
return $differences;
}
// ✅ 使用示例(适配你的原始数据)
$object1 = '[{"acc_number":1001,"acc_name":"admin","acc_loc":"headquarters"}]';
$object2 = '[{"acc_number":1001,"acc_name":"New Admin","acc_loc":"branch"}]';
// 注意:你的数据是数组包裹单个对象,需取索引 0
$oldObj = json_decode($object1, true)[0] ?? [];
$newObj = json_decode($object2, true)[0] ?? [];
$result = getJsonDifferences($oldObj, $newObj);
print_r($result);
?>输出结果:
立即学习“PHP免费学习笔记(深入)”;
Array
(
[0] => Array
(
[key_name] => acc_name
[old_value] => admin
[new_value] => New Admin
)
[1] => Array
(
[key_name] => acc_loc
[old_value] => headquarters
[new_value] => branch
)
)⚠️ 关键注意事项
-
严格比较(
!==):避免"1" == 1导致误判,确保数值、字符串、布尔、null 的类型一致性; -
键存在性检查:使用
array_key_exists()而非isset(),防止null值被跳过; - JSON 输入容错:自动处理字符串、数组、对象三种输入形态,提升复用性;
- 嵌套支持预留:函数设计为递归友好,可轻松扩展至多层对象比对;
- 错误防御:对非法 JSON 或非数组输入抛出明确异常,便于调试。
? 进阶建议
- 若需比对整个 JSON 数组(如多个账户记录),可在外层封装循环,逐项调用
getJsonDifferences; - 对于超大规模数据,建议结合
json_encode(..., JSON_UNESCAPED_UNICODE | JSON_NUMERIC_CHECK)统一编码后再哈希比对,提升性能; - 生产环境推荐使用成熟库如
symfony/property-access或spatie/data-transfer-object构建强类型比对器。
掌握此方法,你将能精准捕获任意层级的 JSON 数据变更,为系统审计、灰度发布、配置回滚等关键流程提供可靠的数据依据。



















