使用node-stream-zip解析manifest.json报错,寻求替代提取方案
解决zip包内manifest.json解析错误及替代提取方法
问题背景
使用node-stream-zip库读取zip包内的manifest.json时,出现以下错误:
错误信息 : SyntaxError: Unexpected token in JSON at position 0
at JSON.parse ()
at StreamZip.
已验证manifest.json格式正确,但同步读取解析时失败,以下是几种可行的替代提取方法:
前置排查点
在更换方法前,先排查两个常见问题:
- BOM头问题:部分JSON文件开头带有UTF-8 BOM(
\ufeff),直接toString('utf8')会保留该字符,导致JSON解析失败。可以先去除:const content = zip.entryDataSync("manifest.json").toString('utf8').replace(/^\uFEFF/, ''); const zipDotTxtContents = JSON.parse(content); - entry路径大小写:zip内的文件路径可能区分大小写,确认
"manifest.json"的路径和大小写完全匹配。
替代提取方法
方法1:改用异步读取entry数据
避免同步方法可能的潜在问题,使用异步方式读取:
const getAppInfoFromManifest = async (productId) => { return new Promise((resolve, reject) => { const zip = new StreamZip({ file: `./files/package${productId}.zip`, storeEntries: true, }); zip.on("ready", () => { zip.entryData("manifest.json", (err, data) => { if (err) { console.log(`读取manifest失败: ${err}`); return resolve(undefined); } try { const content = data.toString('utf8').replace(/^\uFEFF/, ''); const manifest = JSON.parse(content); zip.close(); resolve({ appId: manifest?.id, appVersionManifest: manifest?.version }); } catch (parseErr) { console.log(`解析manifest失败: ${parseErr}`); zip.close(); resolve(undefined); } }); }); zip.on("error", (err) => { console.log(err); console.log(`Manifest Parsing Issue for ${productId}`); resolve(undefined); }); }); };
方法2:换用adm-zip库
adm-zip是另一个常用的Node.js zip处理库,API更直观:
const AdmZip = require('adm-zip'); const getAppInfoFromManifest = async (productId) => { try { const zip = new AdmZip(`./files/package${productId}.zip`); const manifestContent = zip.readAsText("manifest.json").replace(/^\uFEFF/, ''); const manifest = JSON.parse(manifestContent); return { appId: manifest?.id, appVersionManifest: manifest?.version }; } catch (err) { console.log(err); console.log(`Manifest Parsing Issue for ${productId}`); return undefined; } };
方法3:先解压文件到临时目录再读取
如果zip包不大,可以先解压到临时目录,再直接读取文件:
const fs = require('fs').promises; const path = require('path'); const AdmZip = require('adm-zip'); const os = require('os'); const getAppInfoFromManifest = async (productId) => { const tempDir = path.join(os.tmpdir(), `temp_${productId}`); try { const zip = new AdmZip(`./files/package${productId}.zip`); zip.extractAllTo(tempDir, true); const manifestPath = path.join(tempDir, "manifest.json"); const content = await fs.readFile(manifestPath, 'utf8'); const manifest = JSON.parse(content.replace(/^\uFEFF/, '')); // 清理临时文件 await fs.rm(tempDir, { recursive: true, force: true }); return { appId: manifest?.id, appVersionManifest: manifest?.version }; } catch (err) { console.log(err); console.log(`Manifest Parsing Issue for ${productId}`); // 即使出错也清理临时文件 await fs.rm(tempDir, { recursive: true, force: true }).catch(() => {}); return undefined; } };
方法4:使用unzipper库流式处理
unzipper支持流式解压,适合处理大文件:
const unzipper = require('unzipper'); const fs = require('fs'); const getAppInfoFromManifest = async (productId) => { return new Promise((resolve, reject) => { fs.createReadStream(`./files/package${productId}.zip`) .pipe(unzipper.Parse()) .on('entry', async (entry) => { if (entry.path === "manifest.json") { let content = ''; entry.on('data', (chunk) => content += chunk.toString('utf8')); entry.on('end', () => { try { const manifest = JSON.parse(content.replace(/^\uFEFF/, '')); resolve({ appId: manifest?.id, appVersionManifest: manifest?.version }); } catch (err) { console.log(`解析manifest失败: ${err}`); resolve(undefined); } }); } else { entry.autodrain(); // 跳过其他文件 } }) .on('error', (err) => { console.log(err); console.log(`Manifest Parsing Issue for ${productId}`); resolve(undefined); }); }); };
内容的提问来源于stack exchange,提问作者Jagdish Padeliya
相关产品推荐
相关产品推荐

