合法 JSON 字串經 json_decode() 解析回傳 null,json_last_error() 回傳 JSON_ERROR_SYNTAX。檢視原始碼無異常,但 Vim 顯示 <feff>、xxd 發現 EF BB BF 三位元組前綴。
| 編碼 | BOM 十六進制 | Vim 顯示 |
|---|---|---|
| UTF-8 | EF BB BF | <feff> |
| UTF-16LE | FF FE | <feff> |
| UTF-16BE | FE FF | <feff> |
| UTF-32LE | FF FE 00 00 | <feff> |
| UTF-32BE | 00 00 FE FF | <feff> |
BOM 原意標示位元組序,但 JSON RFC 8259 規範禁止 BOM。PHP json_decode() 不自動處理 BOM,導致解析失敗。
# 查找含 BOM 檔案 grep -r -l $'\xEF\xBB\xBF' /path/to/project # 十六進制檢視 xxd suspicious.json | head -5
function hasBom($filepath) {
$fp = fopen($filepath, 'rb');
$bom = fread($fp, 3);
fclose($fp);
return $bom === "\xEF\xBB\xBF";
}
// 批次掃描
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator('/path'));
foreach ($iterator as $file) {
if ($file->isFile() && hasBom($file->getPathname())) {
echo "發現 BOM: " . $file->getPathname() . PHP_EOL;
}
}
:set nobomb " 移除 BOM :w " 儲存 :set bomb? " 查詢狀態
function removeBom($filepath) {
$content = file_get_contents($filepath);
if (substr($content, 0, 3) === "\xEF\xBB\xBF") {
$content = substr($content, 3);
file_put_contents($filepath, $content);
return true;
}
return false;
}
// 遞迴處理目錄
function cleanDirectory($dir) {
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir));
foreach ($iterator as $file) {
if ($file->isFile() && in_array($file->getExtension(), ['php', 'json', 'js', 'css', 'html', 'xml'])) {
if (removeBom($file->getPathname())) {
echo "已清理: {$file->getPathname()}\n";
}
}
}
}
File → Settings → Editor → File EncodingsGlobal Encoding: UTF-8Project Encoding: UTF-8Add BOM for new files#!/bin/sh
# .git/hooks/pre-commit
files=$(git diff --cached --name-only --diff-filter=ACM)
for file in $files; do
if head -c 3 "$file" | xxd -p | grep -q '^efbbbf'; then
echo "錯誤: $file 含有 UTF-8 BOM,請移除後再提交"
exit 1
fi
done
exit 0
function safeJsonDecode($json, $assoc = false, $depth = 512, $options = 0) {
// 移除可能的 BOM
$json = ltrim($json, "\xEF\xBB\xBF\xFE\xFF\xFF\xFE\x00\x00");
return json_decode($json, $assoc, $depth, $options);
}
// 使用
$data = safeJsonDecode(file_get_contents('config.json'));
charset = utf-8, insert_final_newline = true