最近在重构一个老项目,用PHP给前端提供API接口。本来以为json_encode这种基础函数能有什么幺蛾子,结果被现实狠狠打脸。今天就来聊聊这个看似简单实则暗藏杀机的函数。
先来个最简单的例子:
$data = ['name' => '张三', 'age' => 25];
echo json_encode($data);
输出看起来很美:{"name":"张三","age":25}。这时候你觉得这玩意太简单了,然后就开始作死了。
中文编码的坑
第一个坑来得猝不及防。当我把数据库里的中文数据拿出来编码时,突然发现json里中文变成了"\u5f20\u4e09"这种unicode编码。查了手册才发现要加JSON_UNESCAPED_UNICODE参数:
echo json_encode($data, JSON_UNESCAPED_UNICODE);
这还没完,如果你的PHP版本小于5.4,这个参数还不支持,这时候你就得自己写个函数递归处理数组了。
你以为数字类型总安全了?来试试这个:
$data = ['price' => 8.10];
输出居然是{"price":8.99996}!这是因为浮点数精度问题。解决方案要么用字符串表示金额,要么用number_format处理:
$data['price'] = number_format($data['price'], 2, '.', '');
循环引用的悲剧
有一天我遇到了一个神秘错误:"Recursion detected"。查了半天发现是对象之间互相引用:
class A { public $b; }
$a = new A;
$b = new B;
$a->b = $b;
$b->a = $a;
json_encode($a); // 直接爆炸
这时候要么用JSON_PARTIAL_OUTPUT_ON_ERROR参数(PHP7.3+),要么就得手动清理对象引用。
大整数的忧伤
处理ID的时候又踩坑了。JavaScript的Number类型只能安全表示53位整数,而PHP的整数可以很大:
$data = ['id' => 234567890];
前端拿到的是234567000,最后几位被吃了。解决方案是强制转为字符串:
日期时间的烦恼
数据库里的datetime字段直接json_encode会变成字符串,前端还得再解析。其实可以这样处理:
$data = ['create_time' => new DateTime];
echo json_encode($data, JSON_PARTIAL_OUTPUT_ON_ERROR); // 不行!
最后还是得手动格式化:
$data['create_time'] = $data['create_time']->format(DateTime::ATOM);
二进制数据的尴尬
尝试把图片数据编码成JSON?别闹了:
$data = ['image' => file_get_contents('test.jpg')];
echo json_encode($data); // 可能报错
正确的做法是base64编码:
$data['image'] = base64_encode($data['image']);
深度限制的坑
默认情况下json_encode只能处理512层嵌套的数据。如果你的数据结构很深:
$data = [];
$temp = &$data;
for ($i = 0; $i < 600; $i++) {
$temp['child'] = [];
$temp = &$temp['child'];
}
json_encode($data); // 返回false
这时候要么重构数据结构,要么...还是重构数据结构。
性能优化的思考
在大数据量下,json_encode可能成为性能瓶颈。我曾经处理过一个包含10万条记录的数组,编码耗时2秒多。解决方案:
1. 分批处理
2. 考虑使用MessagePack等替代方案
3. 缓存编码结果
特殊字符的麻烦
当数据里包含特殊字符时:
$data = ['html' => '
echo json_encode($data); // 自动转义
如果不想转义,可以组合使用参数:
echo json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
最后来个实用函数收尾,这是我项目中实际使用的:
function safe_json_encode($value, $options = 0, $depth = 512) {
$encoded = json_encode($value, $options, $depth);
if ($encoded === false && $value && json_last_error() == JSON_ERROR_UTF8) {
}
return $encoded;
}
function utf8ize($mixed) {
if (is_array($mixed)) {
foreach ($mixed as $key => $value) {
$mixed[$key] = utf8ize($value);
}
} elseif (is_string($mixed)) {
return mb_convert_encoding($mixed, "UTF-8", "UTF-8");
}
return $mixed;
}
这个函数解决了大多数编码问题,但记住:没有银弹。在PHP里处理JSON,永远要保持警惕,因为说不定什么时候就会冒出个新坑等着你。