临时调试API:为什么你的JSON返回错误?
\n\n你是否曾在凌晨三点,盯着Postman的空白屏幕,心想:“为什么我的PHP脚本返回的是HTML而不是JSON?”?如果是——你并不孤单。Stack Overflow上关于从PHP脚本返回JSON的话题已经积累了数万次浏览,这并非偶然。每两个初级后端开发者中,就有一个会踩到同样的坑:忘记设置标头、输出垃圾内容、Content-Type错误。
\n\n在本文中,我们不会简单复制论坛上的答案。我们将深入探讨如何正确、快速且无意外地从PHP返回JSON。你将了解为什么exit(json_encode($data))可能会让你失望,如何在序列化过程中节省毫秒级时间,以及当JSON突然“损坏”时该怎么办。
准备好了吗?开始吧。
\n\n正确JSON响应的结构
\n\n从基础开始。以下是如何不应该做:
\n\n<?php\n$data = ['status' => 'ok', 'message' => '你好!'];\necho json_encode($data);\n?>\n\n为什么这不好?首先,没有设置Content-Type标头。浏览器或客户端可能会将响应视为HTML或文本。其次,如果代码之前有任何空格、echo或警告,JSON就会被破坏。
正确的最小模板如下:
\n\n<?php\nheader('Content-Type: application/json; charset=utf-8');\n$data = ['status' => 'ok', 'message' => '你好!'];\necho json_encode($data, JSON_UNESCAPED_UNICODE);\nexit;\n?>\n\n注意JSON_UNESCAPED_UNICODE——它保持西里尔字母可读,而不是将其转换为\\u043F\\u0440\\u0438\\u0432\\u0435\\u0442。而exit则是防止响应后意外输出的保险。
标头:不仅仅是Content-Type
\n\n为了优化JSON性能,再添加几个标头:
\n\nheader('Content-Type: application/json; charset=utf-8');\nheader('X-Content-Type-Options: nosniff');\nheader('Cache-Control: no-cache, must-revalidate');\n\nX-Content-Type-Options: nosniff禁止浏览器“猜测”内容类型——这是安全措施。而Cache-Control对于动态数据很有用,可以防止客户端缓存过时的响应。
错误处理:当JSON_ENCODE失败时
\n\njson_encode函数在失败时返回false。有趣的部分就在这里。如果你只是简单地echo json_encode($data),而得到的是一个空字符串——客户端会感到困惑。
始终检查结果:
\n\n$json = json_encode($data, JSON_UNESCAPED_UNICODE);\nif ($json === false) {\n http_response_code(500);\n echo json_encode(['error' => 'JSON编码失败: ' . json_last_error_msg()]);\n exit;\n}\necho $json;\nexit;\n\n顺便一提,json_last_error_msg()会返回清晰的字符串:“Malformed UTF-8 characters”或“Inf and NaN cannot be JSON encoded”。这能节省数小时的调试时间。
深层嵌套数据与递归
\n\n另一个问题是对象中的循环引用。如果你传递一个包含相互引用的对象,json_encode会抛出错误。解决方案:使用JSON_PARTIAL_OUTPUT_ON_ERROR或预先将对象转换为数组。
$json = json_encode($complexObject, JSON_UNESCAPED_UNICODE | JSON_PARTIAL_OUTPUT_ON_ERROR);\n\n但要小心:部分输出可能不完整。最好修复数据结构。
\n\nPHP中JSON性能优化
\n\n现在进入最精彩的部分——如何加速从PHP返回JSON。
\n\n1. 缓存序列化后的JSON
\n\n如果数据变化不频繁(如参考数据、配置、城市列表),不要每次都重新序列化。将准备好的JSON保存到文件、Memcached