技術(shù)純享版:《不規(guī)范 JSON 怎么辦?三種修復(fù)思路+代碼實現(xiàn)》
開篇:夜色漸濃,佳人亦在
那天晚上,辦公室的燈已經(jīng)滅了大半,只剩幾個工位發(fā)出輕輕的藍(lán)光。中央空調(diào)早就熄了,但顯示器的熱度依然在屏幕前形成一圈圈淡淡的光暈。
我坐在靠窗的位置,剛把代碼提交推送完,正打算收鍵盤走人。
這時,小語走過來,端著還冒著熱氣的速溶咖啡——她果然又是那個留下來最晚的人之一。
“誒~”她蹲在我旁邊的桌子邊上,語氣帶著一絲挫敗,“你這邊有沒有遇到 JSON 字符串明明格式看著沒錯,卻死活 JSON.parse
不過的情況?”
一個普通的錯誤,卻不是普通的崩潰
原來她在調(diào)試一個用戶日志上傳模塊,前端接收到的日志數(shù)據(jù)是從后端來的 JSON 字符串。
問題出在一個看似再平常不過的解析操作上——
const logData = JSON.parse(incomingString);
可是控制臺總是報錯:Unexpected token
。數(shù)據(jù)一眼看去也沒問題,{'name': 'Tom', 'age': 30}
—— 結(jié)構(gòu)清晰,屬性齊全,但偏偏就是“壞掉了”。
她抿了一口咖啡,苦笑,“我知道是引號的問題,可這種數(shù)據(jù)是從破舊的系統(tǒng)里吐出來的,量還特別大,我不可能一個個手動改?!?/p>
風(fēng)起 · JSON.parse 不是萬靈藥
我們一起回顧了她的實現(xiàn)方式。她用的是最基礎(chǔ)的 JSON.parse()
,這是我們在項目里默認(rèn)的處理方式——簡單、直接、快速。
但這個方法對 JSON 格式的要求極其嚴(yán)格:
- 只能使用雙引號
"
- 屬性名必須加引號
- 不容忍任何額外字符或注釋
一旦出現(xiàn)諸如單引號、缺少逗號、多余空格這些“微小過失”,就直接拋錯了。
小語嘆氣,“很多時候這些 JSON 是設(shè)備端拼出來的,不規(guī)范,又沒有錯誤提示,我根本不知道該怎么修?!?/p>
我翻了翻之前的代碼,從夾縫中找出來一張破舊的黃皮紙,我們倆一起瞅了上去,看到上面寫著
function tryParseJSON(jsonString) {
try {
return JSON.parse(jsonString);
} catch (e) {
// 嘗試簡單修復(fù):去除可能的多余字符
const cleaned = jsonString.replace(/[^\x20-\x7E]/g, '').trim();
try {
return JSON.parse(cleaned);
} catch (e2) {
console.error("無法解析JSON:", e2);
return null;
}
}
}
下面?zhèn)渥⒘艘恍行∽郑?span style="font-weight: 700; color: rgb(3, 106, 202);">此法在一些更輕量的場景里,做一些“簡陋修復(fù)“,對于簡單的問題有時能奏效,但對于更復(fù)雜的錯誤,比如混合了單引號和雙引號的情況,只能再實現(xiàn)另一個方法可以做更針對性的修復(fù)方法:
function fixQuotes(jsonString) {
// 將單引號替換為雙引號(簡單情況)
return jsonString.replace(/'/g, '"');
}
小語感嘆一聲:“沒有更好的了嗎?”
解決篇 · 來自大佬的一句話
恰好這時,阿杰從會議室出來,耳機(jī)還掛在脖子上。
他聽了一耳朵后隨口說了句:“你們試過 jsonrepair
嗎?那玩意能把壞 JSON 修回來,就像修車?!?/p>
“json... repair?”小語一臉困惑。
我忽然想起,之前有個日志監(jiān)控服務(wù)也碰到類似的問題,當(dāng)時就是用了這個庫一把梭。
我打開編輯器,快速翻出來了這一段:
npm install jsonrepair
const { jsonrepair } = require('jsonrepair');
const damaged = "{name: 'John', age: 30}";
const fixed = jsonrepair(damaged); // => {"name":"John","age":30}
const obj = JSON.parse(fixed);
小語湊過來看了一眼,眼睛一亮:“它真的把引號補好了?”
我點頭。這個工具是為了解決類似“非標(biāo)準(zhǔn) JSON”問題的,它會盡可能地補全缺失引號、逗號,甚至處理 Unicode 異常字符。
當(dāng)然,也不是所有情況都適用。
比如碰到亂碼或者非法嵌套結(jié)構(gòu),jsonrepair
有時也會無能為力。這時可以退一步——用更寬松的解析器,比如 JSON5
:
const JSON5 = require('json5');
const result = JSON5.parse("{name: 'John', age: 30}"); // 也能解析
我看著認(rèn)真學(xué)習(xí)的小語,語重心長的講道:它不是修復(fù),而是擴(kuò)展 JSON 標(biāo)準(zhǔn),讓一些非標(biāo)準(zhǔn)寫法也能解析(JSON5 能容忍的內(nèi)容包括:單引號、尾逗號、注釋、未加引號的屬性名、十六進(jìn)制、科學(xué)計數(shù)法等數(shù)字格式),
接著我們還討論了更復(fù)雜的修復(fù)方式,比如用正則處理批量日志,甚至用 AST 工具逐步構(gòu)建 JSON 樹。但那是更遠(yuǎn)的故事了。
面對當(dāng)前的問題,我們準(zhǔn)備搞一套組合拳:
function parseJson(jsonString) {
// 第一步:嘗試標(biāo)準(zhǔn)JSON解析
try {
return JSON.parse(jsonString);
} catch (e) {
console.log("標(biāo)準(zhǔn)JSON解析失敗,嘗試修復(fù)...");
// 第二步:嘗試使用jsonrepair修復(fù)
try {
const { jsonrepair } = require('jsonrepair');
const fixedJson = jsonrepair(jsonString);
return JSON.parse(fixedJson);
} catch (e2) {
console.log("修復(fù)失敗,嘗試使用JSON5解析...");
// 第三步:嘗試使用JSON5解析
try {
const JSON5 = require('json5');
return JSON5.parse(jsonString);
} catch (e3) {
// 最后:如果所有方法都失敗,返回錯誤信息
console.error("所有解析方法都失敗了:", e3);
throw new Error("無法解析JSON數(shù)據(jù)");
}
}
}
}
結(jié)局
一段時間后,小語在前端監(jiān)控日志里貼了段截圖:原本一天上千條的 parse error
錯誤,幾乎消失了。
她補了一句:“終于不用再一個個點開調(diào)日志了?!?/p>
我回頭看她的工位,屏幕亮著,瀏覽器里是一個模擬器頁面,console 正在緩緩輸出內(nèi)容。
她突然抬起頭看著我,問道:“AST是什么?聽說也能實現(xiàn)json修復(fù)?”