使用FastJSON解析JSON發(fā)生異常的踩坑記錄
前言
最近在開(kāi)發(fā)電子簽名項(xiàng)目時(shí)遇到了一個(gè)特別頭疼的問(wèn)題,系統(tǒng)在處理JSON數(shù)據(jù)時(shí)突然報(bào)錯(cuò),錯(cuò)誤信息看起來(lái)很奇怪:syntax error : f。經(jīng)過(guò)一番排查,發(fā)現(xiàn)是FastJSON在處理特定數(shù)據(jù)時(shí)的一個(gè)坑。今天把這個(gè)問(wèn)題記錄下來(lái),希望能幫到遇到類似問(wèn)題的朋友。
問(wèn)題現(xiàn)象
系統(tǒng)運(yùn)行得好好的,突然就報(bào)錯(cuò)了:
com.alibaba.fastjson2.JSONException: syntax error : f
at com.alibaba.fastjson2.JSONReaderUTF16.readBoolValue(JSONReaderUTF16.java:6426)
at com.alibaba.fastjson2.JSONReader.read(JSONReader.java:2164)
at com.alibaba.fastjson2.JSON.parse(JSON.java:67)
at com.alibaba.fastjson2.JSON.toJSON(JSON.java:3506)
看到這個(gè)錯(cuò)誤,第一反應(yīng)是:什么鬼?JSON格式?jīng)]問(wèn)題啊,怎么就解析不了了?
問(wèn)題代碼
出問(wèn)題的代碼很簡(jiǎn)單,就是想從返回的數(shù)據(jù)中提取一個(gè)字段:
// 這行代碼出問(wèn)題了
com.alibaba.fastjson2.JSONObject contentJson =
(com.alibaba.fastjson2.JSONObject) com.alibaba.fastjson2.JSON.toJSON(invoke.getData().getContent());
String edocId = null;
edocId = contentJson.getString("formRecordId");
if (StringUtils.isBlank(edocId)) {
edocId = contentJson.getString("formRecordCode");
}
數(shù)據(jù)分析
接口返回的JSON數(shù)據(jù)看起來(lái)完全正常:
{
"id": 8745074954268373736,
"startAccountId": -8901286526055593580,
"generateType": null,
"prevNodeId": 175688901899730,
"formRecordId": 6003933439012269528,
"pcPageParam": null,
"dealTime": 0,
"nodeName": "管理員",
"formRecordCode": "6003933439012269528",
"extLong1": 0,
"overdueWorkingShow": null
}
乍一看沒(méi)什么問(wèn)題,但仔細(xì)觀察會(huì)發(fā)現(xiàn),formRecordId 的值是一個(gè)很大的數(shù)字:6003933439012269528。
問(wèn)題根源
經(jīng)過(guò)反復(fù)測(cè)試和分析,發(fā)現(xiàn)問(wèn)題出在FastJSON處理大數(shù)字時(shí)的一個(gè)坑:
- 大數(shù)字處理問(wèn)題:當(dāng)JSON中包含超出JavaScript安全整數(shù)范圍的數(shù)字時(shí),F(xiàn)astJSON在某些情況下會(huì)出現(xiàn)解析異常
- 類型轉(zhuǎn)換問(wèn)題:直接使用
JSON.toJSON()方法可能會(huì)觸發(fā)內(nèi)部的類型推斷機(jī)制,導(dǎo)致解析錯(cuò)誤 - 字符編碼問(wèn)題:在某些環(huán)境下,字符編碼也可能影響JSON解析
解決方案
方案一:避免使用 JSON.toJSON()
最直接的解決方法就是改變JSON處理方式:
// 原來(lái)的寫(xiě)法(有問(wèn)題)
com.alibaba.fastjson2.JSONObject contentJson =
(com.alibaba.fastjson2.JSONObject) com.alibaba.fastjson2.JSON.toJSON(invoke.getData().getContent());
// 改進(jìn)后的寫(xiě)法
String contentStr = com.seeyon.boot.util.JsonUtils.toJson(invoke.getData().getContent());
com.alibaba.fastjson2.JSONObject contentJson = com.alibaba.fastjson2.JSON.parseObject(contentStr);
方案二:統(tǒng)一使用Hutool工具類
既然FastJSON容易踩坑,不如換個(gè)更穩(wěn)定的工具:
// 使用Hutool的JSON工具
cn.hutool.json.JSONObject contentJson = new cn.hutool.json.JSONObject(invoke.getData().getContent());
String edocId = contentJson.getStr("formRecordId");
if (StrUtil.isBlank(edocId)) {
edocId = contentJson.getStr("formRecordCode");
}
方案三:增加異常處理
無(wú)論用什么方案,都要加上異常處理,讓程序更健壯:
private String invokeGetEdocIdByAffairId(Long affairId) {
// ... 前面的代碼省略
try {
// 使用更安全的JSON處理方式
String contentStr = JsonUtil.toJsonStr(invoke.getData().getContent());
cn.hutool.json.JSONObject contentJson = JSONUtil.parseObj(contentStr);
String edocId = contentJson.getStr("formRecordId");
if (StrUtil.isBlank(edocId)) {
edocId = contentJson.getStr("formRecordCode");
}
log.info("成功提取edocId: {}", edocId);
return edocId;
} catch (Exception e) {
log.error("解析JSON數(shù)據(jù)失敗,原始數(shù)據(jù): {}",
invoke.getData().getContent(), e);
return null;
}
}
為什么選擇Hutool
穩(wěn)定性更好:Hutool的JSON工具經(jīng)過(guò)了大量實(shí)際項(xiàng)目的驗(yàn)證,對(duì)各種邊界情況處理得更好
API更友好:getStr() 方法比 getString() 更安全,不會(huì)因?yàn)轭愋蛦?wèn)題拋異常
性能不錯(cuò):雖然不是最快的,但在大多數(shù)場(chǎng)景下性能完全夠用
維護(hù)成本低:API設(shè)計(jì)簡(jiǎn)潔,出問(wèn)題的概率更小
經(jīng)驗(yàn)總結(jié)
避免直接使用 JSON.toJSON():這個(gè)方法在處理復(fù)雜對(duì)象時(shí)容易出問(wèn)題
大數(shù)字要小心:超出JavaScript安全整數(shù)范圍的數(shù)字可能會(huì)導(dǎo)致解析異常
選擇合適的工具:FastJSON雖然性能好,但在某些場(chǎng)景下不如Hutool穩(wěn)定
異常處理很重要:JSON解析一定要加try-catch,并記錄詳細(xì)的錯(cuò)誤信息
測(cè)試要充分:要用各種邊界數(shù)據(jù)進(jìn)行測(cè)試,不能只測(cè)試正常情況
結(jié)語(yǔ)
這次踩坑讓我深刻體會(huì)到,選擇技術(shù)工具不能只看性能,穩(wěn)定性和易用性同樣重要。FastJSON在高并發(fā)場(chǎng)景下確實(shí)性能不錯(cuò),但對(duì)于業(yè)務(wù)代碼來(lái)說(shuō),穩(wěn)定性更重要。
到此這篇關(guān)于使用FastJSON解析JSON發(fā)生異常的踩坑記錄的文章就介紹到這了,更多相關(guān)FastJSON解析JSON內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決執(zhí)行maven命令時(shí)提示Process terminated的問(wèn)題
這篇文章主要介紹了解決執(zhí)行maven命令時(shí)提示Process terminated的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09
詳解SpringBoot2.0的@Cacheable(Redis)緩存失效時(shí)間解決方案
這篇文章主要介紹了詳解SpringBoot2.0的@Cacheable(Redis)緩存失效時(shí)間解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04
雪花算法(snowflak)生成有序不重復(fù)ID的Java實(shí)現(xiàn)代碼
雪花算法是一種分布式系統(tǒng)中生成唯一ID的方法,由41位時(shí)間戳、10位機(jī)器碼和12位序列號(hào)組成,具有唯一性、有序性和高效率等優(yōu)點(diǎn),這篇文章主要介紹了雪花算法(snowflak)生成有序不重復(fù)ID的Java實(shí)現(xiàn)的相關(guān)資料,需要的朋友可以參考下2024-11-11
Spring定時(shí)任務(wù)只執(zhí)行一次的原因分析與解決方案
在使用Spring的@Scheduled定時(shí)任務(wù)時(shí),你是否遇到過(guò)任務(wù)只執(zhí)行一次,后續(xù)不再觸發(fā)的情況?這種情況可能由多種原因?qū)е?如未啟用調(diào)度、線程池問(wèn)題、異常中斷等,本文將深入分析Spring定時(shí)任務(wù)只執(zhí)行一次的原因,并提供完整的解決方案,需要的朋友可以參考下2025-03-03
Java 基礎(chǔ)之NIO 學(xué)習(xí)詳解
這篇文章主要介紹了java基礎(chǔ)之NIO介紹及使用,文中有非常詳細(xì)的代碼示例,對(duì)正在學(xué)習(xí)java基礎(chǔ)的小伙伴們有非常好的幫助,需要的朋友可以參考下2021-09-09
java使用htmlparser提取網(wǎng)頁(yè)純文本例子
這篇文章主要介紹了java使用htmlparser提取網(wǎng)頁(yè)純文本例子,需要的朋友可以參考下2014-04-04
SpringBoot 項(xiàng)目使用hutool 工具進(jìn)行 http 接口調(diào)用的處理方
在實(shí)際的開(kāi)發(fā)過(guò)程中一個(gè)互聯(lián)網(wǎng)的項(xiàng)目來(lái)說(shuō) ,有可能會(huì)涉及到調(diào)用外部接口的實(shí)際業(yè)務(wù)場(chǎng)景,下面通過(guò)本文給大家介紹SpringBoot 項(xiàng)目 使用hutool 工具進(jìn)行 http 接口調(diào)用的處理方法,需要的朋友可以參考下2022-06-06
Java JAR 啟動(dòng)內(nèi)存參數(shù)配置指南(從基礎(chǔ)設(shè)置到性能優(yōu)化)
在啟動(dòng) Java 可執(zhí)行 JAR 文件時(shí),合理配置 JVM 內(nèi)存參數(shù)是保障應(yīng)用穩(wěn)定性和性能的關(guān)鍵,本文將系統(tǒng)講解如何通過(guò)命令行參數(shù)、環(huán)境變量等方式指定內(nèi)存配置,并結(jié)合實(shí)際場(chǎng)景提供優(yōu)化建議,感興趣的朋友跟隨小編一起看看吧2025-11-11

