首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP json_decode 回傳 null 解決:BOM (Byte Order Mark) 導致解析失敗完整排查

     編輯:PHP 綜合知識

常見情境:呼叫外部 HTTP API 取得 JSON 字串,json_decode($str, true) 永遠回傳 null,但將回應內容複製到程式碼中硬編碼卻能正常解析。主因通常是 **UTF-8 BOM (EF BB BF)** 隱藏在字串開頭,導致 JSON 解析器誤判格式。本文提供完整偵測與移除方案,並延伸至其他不可見字元處理。

現象重現

$raw = file_get_contents('https://api.example.com/data.json');
var_dump($raw); // string(103) "{"id":1,"name":"測試"}"  ← 外觀正常

$data = json_decode($raw, true);
var_dump($data); // NULL
var_dump(json_last_error()); // 4 (JSON_ERROR_SYNTAX)
var_dump(json_last_error_msg()); // "Syntax error"

偵測 BOM

function hasBom(string $str): bool
{
    return strlen($str) >= 3 && $str[0] === "\xEF" && $str[1] === "\xBB" && $str[2] === "\xBF";
}

// 或使用 pack (可讀性較佳)
function hasBomPack(string $str): bool
{
    return substr($str, 0, 3) === pack('CCC', 0xEF, 0xBB, 0xBF);
}

// 十六進位檢視確認
echo bin2hex(substr($raw, 0, 10)); // efbbbf7b226964223a31 -> 前三位 EF BB BF 即 BOM

移除 BOM 通用函式

/**
 * 移除字串開頭的 UTF-8 BOM (若存在)
 * @param string $str
 * @return string 無 BOM 字串
 */
function stripBom(string $str): string
{
    $bom = pack('CCC', 0xEF, 0xBB, 0xBF);
    if (str_starts_with($str, $bom)) { // PHP 8.0+
        return substr($str, 3);
    }
    // PHP 7 相容
    return substr($str, 0, 3) === $bom ? substr($str, 3) : $str;
}

// 使用
$clean = stripBom($raw);
$data = json_decode($clean, true); // 正常解析

進階:移除所有不可見控制字元 (含 BOM、零寬空格、換行符等)

function sanitizeJsonString(string $str): string
{
    // 1. 移除 BOM
    $str = stripBom($str);

    // 2. 移除零寬空格 (U+200B, U+200C, U+200D, U+FEFF 等)
    $str = preg_replace('/[\x{200B}-\x{200D}\x{FEFF}]/u', '', $str);

    // 3. 移除開頭/結尾空白字元 (含不可見)
    $str = trim($str, "\x00..\x1F\x7F\xA0\x{2000}-\x{200F}\x{2028}\x{2029}\x{202F}\x{205F}\x{3000}\x{FEFF}");

    return $str;
}

完整防禦性解析流程

function safeJsonDecode(string $raw, bool $assoc = true, int $depth = 512, int $flags = 0): mixed
{
    $clean = sanitizeJsonString($raw);
    $result = json_decode($clean, $assoc, $depth, $flags);

    if (json_last_error() !== JSON_ERROR_NONE) {
        // 記錄前 200 字元供除錯
        $preview = mb_substr($clean, 0, 200);
        throw new RuntimeException(
            "JSON 解析失敗:{$preview}... | " . json_last_error_msg()
        );
    }
    return $result;
}

// 使用
try {
    $data = safeJsonDecode(file_get_contents('https://api.example.com/data.json'));
} catch (Throwable $e) {
    error_log($e->getMessage());
    // 降級處理...
}

常見來源與預防

來源原因預防
Windows 記事本儲存 UTF-8預設加 BOM使用 VS Code / Notepad++ 另存「UTF-8 without BOM」
PHP include/require 載入設定檔設定檔含 BOM所有 .php/.json 檔統一無 BOM
外部 API 回應對方伺服器輸出 BOM客戶端統一 stripBom()
資料庫欄位存 JSON寫入時帶入 BOM寫入前 stripBom()
前端 fetch 回應後端輸出 BOM後端 header('Content-Type: application/json; charset=utf-8') 且無 BOM

除錯小技巧

# 終端機快速檢查檔案是否有 BOM
head -c 3 file.json | xxd
# 輸出 ef bb bf 即有 BOM

# 批次移除專案所有檔案 BOM (Linux/macOS)
find . -type f -name '*.php' -o -name '*.json' -o -name '*.js' | xargs -r sed -i '1s/^\xEF\xBB\xBF//'

總結

  • BOM 是隱形殺手:肉眼不見、var_dump 不顯、json_decode 直接失敗。
  • 統一入口清洗:在 HTTP 客戶端封裝層 (Guzzle Middleware / 自訂 Client) 統一呼叫 stripBom(),避免業務碼散落判斷。
  • 編碼規範:專案強制「UTF-8 without BOM」,納入 Code Style 檢查 (PHP_CodeSniffer / EditorConfig)。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved