PHP JSON
json_encode/decode, Chinese characters and escaping, error handling, and array/object conversion.
Encoding
<?php
$data = ["name" => "张三", "age" => 25, "tags" => ["php", "web"]];
echo json_encode($data), "\n";
echo json_encode($data, JSON_UNESCAPED_UNICODE), "\n";
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), "\n";{"name":"\u5f20\u4e09","age":25,"tags":["php","web"]}
{"name":"张三","age":25,"tags":["php","web"]}
{
"name": "张三",
"age": 25,
"tags": [
"php",
"web"
]
}By default non-ASCII becomes \uXXXX. **Use JSON_UNESCAPED_UNICODE for APIs** — smaller and easier to debug; keep the default for files and signatures.
Decoding
<?php
$json = '{"name":"张三","age":25,"tags":["php","web"]}';
$arr = json_decode($json, true);
echo $arr["name"], "\n";
echo count($arr["tags"]), "\n";
echo gettype($arr), "\n";
$obj = json_decode($json);
echo $obj->name, "\n";
echo $obj->tags[0], "\n";
echo gettype($obj), "\n";张三
2
array
张三
php
objectWithout the second argument you get stdClass and use ->. Deep structures get verbose, so most projects pass true.
Error handling
json_decode returns null on failure. **Do not test === null alone** — decoding "null" also gives null. Use json_last_error().
<?php
foreach (['{"a":1}', '{bad}', 'null', ''] as $s) {
$v = json_decode($s, true);
$label = $s === "" ? "(空串)" : $s;
if (json_last_error() === JSON_ERROR_NONE) {
echo "$label => 成功: " . var_export($v, true) . "\n";
} else {
echo "$label => 失败: " . json_last_error_msg() . "\n";
}
}{"a":1} => 成功: array (
'a' => 1,
)
{bad} => 失败: Syntax error
null => 成功: NULL
(空串) => 失败: Syntax errorPHP 8.3 adds json_validate() — checks validity without building structures, saving memory on large payloads.
<?php
var_dump(json_validate('{"a":1}'));
var_dump(json_validate('{bad}'));
$v = json_decode('{"a":1}', false, 512, JSON_THROW_ON_ERROR);
echo $v->a, "\n";
try {
json_decode('{bad}', false, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
echo "异常: " . $e->getMessage(), "\n";
}bool(true)
bool(false)
1
异常: Syntax errorJSON_THROW_ON_ERROR throws JsonException instead of relying on a later check — forgetting that check is the main source of these bugs.Serving a JSON API
<?php
header("Content-Type: application/json; charset=utf-8");
$data = [
"code" => 0,
"msg" => "ok",
"data" => ["id" => 1, "name" => "张三"],
];
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";{"code":0,"msg":"ok","data":{"id":1,"name":"张三"}}Content-Type must be application/json or fetch().json() may refuse. JSON_UNESCAPED_SLASHES keeps / unescaped in URLs.
Common pitfalls
| Symptom | Cause | Fix |
|---|---|---|
json_encode 返回 false | 含非法 UTF-8 字节 | 先 mb_convert_encoding 清洗 |
| 数字变成字符串 | 原值就是字符串(如 "25") | 入库前转 int |
| 小数精度丢失 | PHP float 是双精度 | 金额用整数分存储 |
空数组变 [],空对象变 {} | [] 编码为数组 | 要对象就 (object)[] |
| 中文乱码 | 没设 UTF-8 头 | header("Content-Type: application/json; charset=utf-8") |
<?php
echo json_encode([]), "\n";
echo json_encode((object)[]), "\n";
echo json_encode(["a" => []]), "\n";
echo json_encode(["a" => (object)[]]), "\n";
// 金额用整数分
$cents = 1999;
echo json_encode(["price" => $cents, "yuan" => $cents / 100]), "\n";[]
{}
{"a":[]}
{"a":{}}
{"price":1999,"yuan":19.99}0.1 + 0.2 = 0.30000000000000004. Store integer cents and divide on display — standard in payments.编码
<?php
$data = ["name" => "张三", "age" => 25, "tags" => ["php", "web"]];
echo json_encode($data), "\n";
echo json_encode($data, JSON_UNESCAPED_UNICODE), "\n";
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), "\n";{"name":"\u5f20\u4e09","age":25,"tags":["php","web"]}
{"name":"张三","age":25,"tags":["php","web"]}
{
"name": "张三",
"age": 25,
"tags": [
"php",
"web"
]
}默认会把非 ASCII 字符转成 \uXXXX。**给前端 API 用 JSON_UNESCAPED_UNICODE**,省流量也便于调试;写文件或做签名时保持默认更安全。
解码
<?php
$json = '{"name":"张三","age":25,"tags":["php","web"]}';
$arr = json_decode($json, true);
echo $arr["name"], "\n";
echo count($arr["tags"]), "\n";
echo gettype($arr), "\n";
$obj = json_decode($json);
echo $obj->name, "\n";
echo $obj->tags[0], "\n";
echo gettype($obj), "\n";张三
2
array
张三
php
object不传第二个参数时得到 stdClass 对象,用 -> 访问。深嵌套时对象访问写起来很啰嗦,多数项目统一用 true 转数组。
错误处理
json_decode 失败返回 null。**不能只判断 === null**,因为 "null" 解码也是 null。要用 json_last_error()。
<?php
foreach (['{"a":1}', '{bad}', 'null', ''] as $s) {
$v = json_decode($s, true);
$label = $s === "" ? "(空串)" : $s;
if (json_last_error() === JSON_ERROR_NONE) {
echo "$label => 成功: " . var_export($v, true) . "\n";
} else {
echo "$label => 失败: " . json_last_error_msg() . "\n";
}
}{"a":1} => 成功: array (
'a' => 1,
)
{bad} => 失败: Syntax error
null => 成功: NULL
(空串) => 失败: Syntax errorPHP 8.3 起有 json_validate(),只检查合法性不构建结构,处理大 JSON 时更省内存。
<?php
var_dump(json_validate('{"a":1}'));
var_dump(json_validate('{bad}'));
$v = json_decode('{"a":1}', false, 512, JSON_THROW_ON_ERROR);
echo $v->a, "\n";
try {
json_decode('{bad}', false, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
echo "异常: " . $e->getMessage(), "\n";
}bool(true)
bool(false)
1
异常: Syntax errorJSON_THROW_ON_ERROR 让解码失败抛 JsonException,比事后查 json_last_error() 可靠得多——忘记查错误码是这类 bug 的主要来源。返回 JSON 接口
<?php
header("Content-Type: application/json; charset=utf-8");
$data = [
"code" => 0,
"msg" => "ok",
"data" => ["id" => 1, "name" => "张三"],
];
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";{"code":0,"msg":"ok","data":{"id":1,"name":"张三"}}Content-Type 必须是 application/json,否则前端 fetch().json() 可能拒绝解析。JSON_UNESCAPED_SLASHES 让 URL 里的 / 不被转义成 \/。
常见坑
| 现象 | 原因 | 解决 |
|---|---|---|
json_encode 返回 false | 含非法 UTF-8 字节 | 先 mb_convert_encoding 清洗 |
| 数字变成字符串 | 原值就是字符串(如 "25") | 入库前转 int |
| 小数精度丢失 | PHP float 是双精度 | 金额用整数分存储 |
空数组变 [],空对象变 {} | [] 编码为数组 | 要对象就 (object)[] |
| 中文乱码 | 没设 UTF-8 头 | header("Content-Type: application/json; charset=utf-8") |
<?php
echo json_encode([]), "\n";
echo json_encode((object)[]), "\n";
echo json_encode(["a" => []]), "\n";
echo json_encode(["a" => (object)[]]), "\n";
// 金额用整数分
$cents = 1999;
echo json_encode(["price" => $cents, "yuan" => $cents / 100]), "\n";[]
{}
{"a":[]}
{"a":{}}
{"price":1999,"yuan":19.99}0.1 + 0.2 = 0.30000000000000004 这类误差。数据库存整数分,展示时除以 100,这是支付系统的通行做法。