在 Web 開發中,Blob (Binary Large Object) 對象表示一個不可變、原始數據的類文件對象。它通常用於處理二進制數據,例如圖片、音頻、視頻文件,或者來自網絡請求的原始數據流。當從 API 接收到響應、用户上傳文件,或者進行一些本地數據操作時,你可能會遇到 Blob 格式的數據。

然而,如果這些 Blob 實際上包含了 JSON 格式的文本數據(例如,一個 API 端點可能以 application/json 類型發送數據,但在某些情況下,瀏覽器或 fetch/XMLHttpRequest API 會將其作為 Blob 來處理),那麼我們就需要將其從二進制 Blob 轉換為可用的 JSON 對象。

1. 理解 Blob 數據和轉換的必要性

  • Blob 的本質: Blob 對象是原始的、不可變的二進制數據塊。它不直接提供讀取其內容的方法,而是通過其他 API(如 FileReaderResponse 對象的特定方法)來訪問其內部數據。
  • JSON 的本質: JSON (JavaScript Object Notation) 是一種輕量級的數據交換格式,它本質上是字符串。它被設計用於存儲和傳輸數據。
  • 轉換的必要性: 當我們得到的 Blob 實際是包含 JSON 文本時,我們不能直接對 Blob 執行 JSON.parse(),因為 JSON.parse() 期望的是一個字符串。因此,我們需要一箇中間步驟:將 Blob 中的二進制數據解碼成文本字符串,然後再將這個字符串解析成 JSON 對象。

2. 核心轉換機制:FileReader API

FileReader 是一個 Web API,它允許 Web 應用程序異步讀取存儲在用户計算機上的文件(或原始數據緩衝區,如 Blob)的內容。它提供了幾種讀取數據的方法,其中最常用的是:

  • readAsText(blob, encoding): 將 Blob 或 File 的內容讀取為文本字符串。
  • readAsDataURL(blob): 將 Blob 或 File 的內容讀取為 Data URL。
  • readAsArrayBuffer(blob): 將 Blob 或 File 的內容讀取為 ArrayBuffer。

對於將 Blob 轉換為 JSON,我們主要使用 readAsText() 方法。

3. 將 Blob 轉換為 JSON 的步驟

  1. 創建 FileReader 實例。
  2. 調用 readAsText() 方法,傳入你的 Blob 對象。
  3. 監聽 loadend 事件:當讀取操作完成時(無論是成功或失敗),會觸發 loadend 事件。讀取成功時,FileReader.result 屬性將包含 Blob 的內容(作為文本字符串)。
  4. 使用 JSON.parse() 解析文本:將 FileReader.result 中的文本字符串解析為 JavaScript 對象。

4. 代碼示例

方法一:使用 FileReader (異步方式)

這是最通用的方法,適用於任何 Blob 對象,包括從文件輸入或某些 API 響應中獲取的 Blob

/**
 * 將 Blob 數據轉換為 JSON 對象。
 * @param {Blob} blob - 要轉換的 Blob 對象。
 * @returns {Promise<Object>} - 解析為 JSON 對象的 Promise。
 */
function blobToJson(blob) {
  return new Promise((resolve, reject) => {
    if (!(blob instanceof Blob)) {
      return reject(new Error('Input is not a Blob object.'));
    }

    // 檢查 Blob 的類型是否可能是 JSON
    // 這不是強制性的,但有助於提前判斷
    if (blob.type && !blob.type.includes('json')) {
      console.warn(`Blob type is ${blob.type}, not explicitly JSON. Attempting conversion anyway.`);
    }

    const reader = new FileReader();

    // 當讀取操作成功完成時
    reader.onload = function(event) {
      try {
        const jsonString = event.target.result;
        const jsonObject = JSON.parse(jsonString);
        resolve(jsonObject);
      } catch (e) {
        reject(new Error(`Failed to parse JSON from Blob: ${e.message}`));
      }
    };

    // 當讀取操作發生錯誤時
    reader.onerror = function() {
      reject(new Error('Failed to read Blob data.'));
    };

    // 開始將 Blob 內容讀取為文本
    // 可以指定編碼,例如 'UTF-8',如果 Blob 類型沒有指定
    reader.readAsText(blob);
  });
}

// --- 示例用法 ---

// 1. 模擬一個包含 JSON 文本的 Blob
const jsonText = JSON.stringify({
  message: 'Hello from Blob!',
  data: [1, 2, 3],
  timestamp: new Date().toISOString()
});
const jsonBlob = new Blob([jsonText], { type: 'application/json' });

// 調用轉換函數
blobToJson(jsonBlob)
  .then(data => {
    console.log('轉換成功 (使用 FileReader):', data);
    console.log('數據類型:', typeof data);
    console.log('實際值:', data.message);
  })
  .catch(error => {
    console.error('轉換失敗 (使用 FileReader):', error);
  });

// 2. 模擬一個非 JSON 文本的 Blob (會導致解析錯誤)
const nonJsonTextBlob = new Blob(['This is not JSON.'], { type: 'text/plain' });
blobToJson(nonJsonTextBlob)
  .then(data => {
    console.log('成功解析非 JSON 數據:', data); // 理論上不應該到這裏
  })
  .catch(error => {
    console.error('轉換失敗 (非 JSON 文本):', error.message); // 預期錯誤
  });

// 3. 模擬一個空的 Blob
const emptyBlob = new Blob([], { type: 'application/json' });
blobToJson(emptyBlob)
    .then(data => console.log('轉換成功 (空 Blob):', data)) // 預期會解析為 {}
    .catch(error => console.error('轉換失敗 (空 Blob):', error));
方法二:使用 Response.json() (Fetch API 專用)

如果你是通過 Fetch API 獲取到的 Response 對象,並且你預期響應內容是 JSON,那麼可以直接使用 Response 對象提供的 json() 方法。這個方法會自動處理將響應體讀取為文本,然後解析為 JSON 的過程,並且它返回一個 Promise。

// 模擬一個 Fetch API 的 Response 對象
async function simulateFetchResponse() {
  const jsonText = JSON.stringify({
    status: 'success',
    code: 200,
    payload: { id: 'abc', value: 'data' }
  });

  // 創建一個 Response 對象,模擬網絡響應
  const response = new Response(jsonText, {
    headers: { 'Content-Type': 'application/json' },
    status: 200
  });

  // 假設 response.blob() 已經被調用,我們現在有一個 Blob
  const blobData = await response.blob();
  console.log('原始 Blob 對象 (從模擬 Response.blob() 獲取):', blobData);

  // 重要:實際中你不會先調用 .blob() 再調用 .json()
  // 你會直接從 Response 對象調用 .json()
  // 如下所示:
  const actualResponse = new Response(jsonText, {
    headers: { 'Content-Type': 'application/json' },
    status: 200
  });

  try {
    const jsonData = await actualResponse.json(); // 直接調用 .json()
    console.log('轉換成功 (使用 Response.json()):', jsonData);
    console.log('數據類型:', typeof jsonData);
  } catch (error) {
    console.error('轉換失敗 (使用 Response.json()):', error);
  }
}

// 運行模擬 Fetch 響應
simulateFetchResponse();

// 真實世界的 Fetch API 用法
/*
fetch('https://api.example.com/data')
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return response.json(); // 如果期望 JSON 響應,直接調用 .json()
  })
  .then(data => {
    console.log('從 API 獲取並解析的 JSON:', data);
  })
  .catch(error => {
    console.error('Fetch API 錯誤:', error);
  });
*/

5. 常見應用場景

  1. 處理 fetch API 響應:fetch 響應的 Content-Type 頭部不是 application/json,但你知道它實際上包含 JSON 時(或者只是為了以防萬一),你可以先調用 response.blob() 獲取 Blob,然後再用 FileReader 轉換。但更推薦的始終是直接使用 response.json()
  2. 處理用户上傳的文件: 如果用户上傳了一個 .json 文件,你可以通過 FileReader 讀取其內容。
<input type="file" id="fileInput" accept=".json">
<script>
  document.getElementById('fileInput').addEventListener('change', function(event) {
    const file = event.target.files[0];
    if (file) {
      blobToJson(file) // File 對象是 Blob 的一個子類型
        .then(json => console.log('上傳的 JSON 文件內容:', json))
        .catch(err => console.error('讀取或解析文件失敗:', err));
    }
  });
</script>
  1. IndexedDB 存儲和檢索: 如果你將 JSON 數據作為 Blob 存儲在 IndexedDB 中,檢索時也需要進行轉換。

6. 錯誤處理和注意事項

  • 異步操作: FileReader 是異步的。這意味着 blobToJson 函數必須返回一個 Promise,以便你可以使用 .then().catch() 來處理結果。
  • 非 JSON 內容: 如果 Blob 實際上不包含有效的 JSON 字符串,JSON.parse() 將會拋出 SyntaxError。確保你的錯誤處理能夠捕獲並優雅地處理這種情況。
  • 編碼: reader.readAsText(blob, encoding) 的第二個參數 encoding 允許你指定文本編碼(例如 'UTF-8')。如果 Blobtype 屬性中包含 charset 信息(例如 application/json; charset=UTF-8),FileReader 會自動使用它。否則,它會使用系統默認編碼。通常情況下,JSON 都是 UTF-8 編碼的。
  • 內存使用: 對於非常大的 Blob,將整個內容讀取到內存中作為一個字符串可能導致性能問題或內存溢出。在這種情況下,可能需要考慮流式處理,但這超出了直接將 Blob 轉換為 JSON 的範疇。

通過理解 Blob 的特性和 FileReader 的工作原理,你可以高效且健壯地將包含 JSON 數據的 Blob 對象轉換為可用的 JavaScript JSON 對象。