Java 手動解析不帶引號的JSON字符串的操作
1 需求說明
項目中遇到了一批不帶引號的類JSON格式的字符串:
{Name:Heal,Age:20,Tag:[Coding,Reading]}
需要將其解析成JSON對象, 然后插入到Elasticsearch中, 當(dāng)作Object類型的對象存儲起來.
在對比了阿里的FastJson、Google的Gson, 沒找到想要的功能 ( 可能是博主不夠仔細(xì), 有了解的童學(xué)留言告訴我下呀😛), 于是就自己寫了個工具類, 用來實現(xiàn)此需求.
如果是帶有引號的標(biāo)準(zhǔn)JSON字符串, 可直接通過上述2種工具進(jìn)行解析, 使用方法可參考:
2 解析代碼
2.1 實現(xiàn)思路
代碼的主要思路在注釋中都有說明, 主要思路是:
(1) 借助Stack統(tǒng)計字符串首尾的[]、{}符號 —— []代表List, {}代表Map;
(2) 使用String#subString()方法縮減已解析的字符串;
(3) 使用遞歸解析內(nèi)部的List、Map對象;
(4) 為了便于處理, 最小的key-value都解析成String類型.
需要注意的是: 要解析的字符串內(nèi)部不要存在無意義的{、}、[、]符號, 否則會導(dǎo)致解析發(fā)生異常.
—— 暫時沒想到好的兼容方法, 有想法的童學(xué)請直接留言.**
2.2 詳細(xì)代碼
package com.healchow.util;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
/**
* Java 解析不帶引號的JSON字符串
*
* @author Heal Chow
* @date 2019/08/13 11:36
*/
public class ParseJsonStrUtils {
public static void main(String[] args) {
// 帶引號的字符串, 會將字符串當(dāng)作key-value的一部分, 因此這類字符串推薦使用fastJson、Gson等工具轉(zhuǎn)換
// 注意: String內(nèi)部不要存在無意義的{、}、[、]符號 - 暫時沒想到好的兼容方法
/*String sourceStr = "{\"_index\":\"book_shop\"," +
"\"_id\":\"1\"," +
"\"_source\":{" +
"\"name\":\"Thinking in Java [4th Edition]\"," +
"\"author\":\"[US] Bruce Eckel\"," +
"\"price\":109.0,\"date\":\"2007-06-01 00:00:00\"," +
"\"tags\":[\"Java\",[\"Programming\"]" +
"}}";*/
// 不帶引號的字符串, 首尾多對[]、{}不影響解析
String sourceStr = "[[[{" +
"{" +
"Type:1," +
"StoragePath:[{Name:/image/2019-08-01/15.jpeg,DeviceID:4401120000130},{ShotTime:2019-08-01 14:44:24}]," +
"Width:140" +
"}," +
"{" +
"Type:2,StoragePath:9090/pic/2019_08_01/src.jpeg," +
"Inner:{DeviceID:44011200}," +
"Test:[{ShotTime:2019-08-01 14:50:14}]," +
"Width:5600}" +
"}}]]]";
List<Map<String, Object>> jsonArray;
Map<String, Object> jsonMap;
Object obj = null;
try {
obj = parseJson(sourceStr);
} catch (Exception e) {
System.out.println("出錯啦: " + e.getMessage());
e.printStackTrace();
}
if (obj instanceof List) {
jsonArray = (List<Map<String, Object>>) obj;
System.out.println("解析生成了List對象: " + jsonArray);
} else if (obj instanceof Map) {
jsonMap = (Map<String, Object>) obj;
System.out.println("解析生成了Map對象: " + jsonMap);
} else {
System.out.println("需要解析的字符串既不是JSON Array, 也不符合JSON Object!\n原字符串: " + sourceStr);
}
}
/**
* 解析 Json 格式的字符串, 封裝為 List 或 Map 并返回
* 注意: (1) key 和 value 不能含有 ",", key 中不能含有 ":" —— 要分別用 "," 和 ":" 進(jìn)行分隔
* (2) 要解析的字符串必須符合JSON對象的格式, 只對最外層的多層嵌套做了簡單的處理,
* 復(fù)雜的如"{a:b},{x:y}"將不能完全識別 —— 正確的應(yīng)該是"[{a:b},{x:y}]"
* @param sourceStr 首尾被"[]"或"{}"包圍的字符串
* @return 生成的JsonObject
*/
public static Object parseJson(String sourceStr) throws InvalidParameterException {
if (sourceStr == null || "".equals(sourceStr)) {
return sourceStr;
}
// 判斷字符串首尾有沒有多余的、相匹配的 "[]" 和 "{}"
String parsedStr = simplifyStr(sourceStr, "[", "]");
parsedStr = simplifyStr(parsedStr, "{", "}");
// 借助棧來實現(xiàn) "[]" 和 "{}" 的出入
Stack<String> leftSymbolStack = new Stack<>();
Stack<String> rightSymbolStack = new Stack<>();
if ((parsedStr.startsWith("[") && parsedStr.endsWith("]")) || (parsedStr.startsWith("{") && parsedStr.endsWith("}"))) {
leftSymbolStack.push(parsedStr.substring(0, 1));
rightSymbolStack.push(parsedStr.substring(parsedStr.length() - 1));
parsedStr = parsedStr.substring(1, parsedStr.length() - 1);
// parsedStr 內(nèi)部還可能是連續(xù)的"{{}}"
parsedStr = simplifyStr(parsedStr, "{", "}");
} else {
throw new InvalidParameterException("要解析的字符串中存在不匹配的'[]'或'{}', 請檢查!\n原字符串為: " + sourceStr);
}
// 保存解析的結(jié)果, jsonArray中可能只有String, 也可能含有Map<String, Object>
List<Object> jsonArray = new ArrayList<>();
Map<String, Object> jsonMap = new HashMap<>(16);
// 內(nèi)部遍歷、解析
innerParseByLoop(parsedStr, leftSymbolStack, rightSymbolStack, jsonArray, jsonMap);
// 判斷jsonArray是否為空
if (jsonArray.size() > 0) {
return jsonArray;
} else {
return jsonMap;
}
}
/**
* 循環(huán)解析內(nèi)部的List、Map對象
*/
private static void innerParseByLoop(String parsedStr, Stack<String> leftSymbolStack, Stack<String> rightSymbolStack,
List<Object> jsonArray, Map<String, Object> jsonMap) throws InvalidParameterException {
if (parsedStr == null || parsedStr.equals("")) {
return;
}
// 按照","分隔
String[] allKeyValues = parsedStr.split(",");
if (allKeyValues.length > 0) {
// 遍歷, 并按照":"分隔解析
out:
for (String keyValue : allKeyValues) {
// 如果keyValue中含有":", 說明該keyValue是List<Map>中的一個對象, 就需要確定第一個":"的位置 —— 可能存在多個":"
int index = keyValue.indexOf(":");
if (index > 0) {
// 判斷key是否仍然以"{"或"["開始, 如果是, 則壓棧
String key = keyValue.substring(0, index);
while (key.startsWith("[") || key.startsWith("{")) {
leftSymbolStack.push(key.substring(0, 1));
// 解析過的串要一直跟進(jìn)
parsedStr = parsedStr.substring(1);
key = key.substring(1);
}
// 判讀和value是否以"["開頭 —— 又是一個 List 對象 —— 遞歸解析
String value = keyValue.substring(index + 1);
if (value.startsWith("[")) {
int innerIndex = parsedStr.indexOf("]");
List<Object> innerList = (List<Object>) parseJson(parsedStr.substring(key.length() + 1, innerIndex + 1));
jsonMap.put(key, innerList);
// 清除最后的"]", 并判斷是否存在","
parsedStr = parsedStr.substring(innerIndex + 1);
if (parsedStr.indexOf(",") == 0) {
parsedStr = parsedStr.substring(1);
}
// 此內(nèi)部存在對象, 內(nèi)部的","已經(jīng)解析完畢了, 要修正按照","切割的字符串?dāng)?shù)組, 并繼續(xù)遍歷
innerParseByLoop(parsedStr, leftSymbolStack, rightSymbolStack, jsonArray, jsonMap);
break;
}
// 判讀和value是否以 "{" 開頭 —— 又是一個 Map 對象 —— 遞歸解析
else if (value.startsWith("{")) {
int innerIndex = parsedStr.indexOf("}");
Map<String, Object> innerMap = (Map<String, Object>) parseJson(parsedStr.substring(key.length() + 1, innerIndex + 1));
jsonMap.put(key, innerMap);
// 清除最后的"}", 并判斷是否存在","
parsedStr = parsedStr.substring(innerIndex + 1);
if (parsedStr.indexOf(",") == 0) {
parsedStr = parsedStr.substring(1);
}
// 此內(nèi)部存在對象, 內(nèi)部的","已經(jīng)解析完畢了, 要修正按照","切割的字符串?dāng)?shù)組, 并繼續(xù)遍歷
innerParseByLoop(parsedStr, leftSymbolStack, rightSymbolStack, jsonArray, jsonMap);
break;
}
// 最后判斷value尾部是否含有 "]" 或 "}"
else {
while (value.endsWith("]") || value.endsWith("}")) {
// 最右側(cè)的字符
String right = value.substring(value.length() - 1);
// 此時棧頂元素
String top = leftSymbolStack.peek();
// 如果有相匹配的, 則彈棧, 否則忽略
if (("}".equals(right) && "{".equals(top)) || ("]".equals(right) && "[".equals(top))) {
leftSymbolStack.pop();
value = value.substring(0, value.length() - 1);
jsonMap.put(key, value);
// 清除最后的"}", 并判斷是否存在","
parsedStr = parsedStr.substring(key.length() + 1 + value.length() + 1);
if (parsedStr.indexOf(",") == 0) {
parsedStr = parsedStr.substring(1);
}
// 解析完成了一個對象, 則將該元素添加到List中, 并創(chuàng)建新的對象
jsonArray.add(jsonMap);
jsonMap = new HashMap<>(16);
// 繼續(xù)進(jìn)行外層的解析
continue out;
}
// 如果都不匹配, 則有可能是源字符串的最后一個符號
else {
rightSymbolStack.push(right);
value = value.substring(0, value.length() - 1);
}
}
jsonMap.put(key, value);
int length = key.length() + value.length() + 2;
if (parsedStr.length() > length) {
parsedStr = parsedStr.substring(length);
} else {
parsedStr = "";
}
}
}
// 如果keyValue中不含":", 說明該keyValue只是List<String>中的一個串, 而非List<Map>中的一個Map, 則直接將其添加到List中即可
else {
jsonArray.add(keyValue);
}
}
// 遍歷結(jié)束, 處理最后的符號問題 —— 判斷左右棧是否匹配
while (!leftSymbolStack.empty()) {
if (leftSymbolStack.peek().equals("{") && parsedStr.equals("}")) {
leftSymbolStack.pop();
}
if (!rightSymbolStack.empty()) {
if (leftSymbolStack.peek().equals("{") && rightSymbolStack.peek().equals("}")) {
leftSymbolStack.pop();
rightSymbolStack.pop();
} else if (leftSymbolStack.peek().equals("[") && rightSymbolStack.peek().equals("]")) {
leftSymbolStack.pop();
rightSymbolStack.pop();
} else {
throw new InvalidParameterException("傳入的字符串中不能被解析成JSON對象!\n原字符串為: " + parsedStr);
}
}
}
}
}
/**
* 判斷字符串首尾有沒有多余的、相匹配的 "[]" 和 "{}", 對其進(jìn)行簡化
*/
private static String simplifyStr(String sourceStr, String firstSymbol, String lastSymbol) {
while (sourceStr.startsWith(firstSymbol) && sourceStr.endsWith(lastSymbol)) {
String second = sourceStr.substring(1, 2);
// 如果第二個仍然是"["或"{", 再判斷倒數(shù)第二個是不是"]"或"}" —— 說明長度至少為3, 不會發(fā)生 IndexOutOfBoundsException
if (second.equals(firstSymbol)) {
String penultimate = sourceStr.substring(sourceStr.length() - 2, sourceStr.length() - 1);
if (penultimate.equals(lastSymbol)) {
// 縮短要解析的串
sourceStr = sourceStr.substring(1, sourceStr.length() - 1);
} else {
break;
}
} else {
break;
}
}
return sourceStr;
}
}
2.3 測試樣例
(1) 帶引號的測試:
// 測試字符串:
String sourceStr = "{\"_index\":\"book_shop\"," +
"\"_id\":\"1\"," +
"\"_source\":{" +
"\"name\":\"Thinking in Java [4th Edition]\"," +
"\"author\":\"[US] Bruce Eckel\"," +
"\"price\":109.0,\"date\":\"2007-06-01 00:00:00\"," +
"\"tags\":[\"Java\",[\"Programming\"]" +
"}}";
解析結(jié)果為:

(2) 不帶引號的測試:
// 測試字符串:
String sourceStr = "[[[{" +
"{" +
"Type:1," +
"StoragePath:[{Name:/image/2019-08-01/15.jpeg,DeviceID:4401120000130},{ShotTime:2019-08-01 14:44:24}]," +
"Width:140" +
"}," +
"{" +
"Type:2,StoragePath:9090/pic/2019_08_01/src.jpeg," +
"Inner:{DeviceID:44011200}," +
"Test:[{ShotTime:2019-08-01 14:50:14}]," +
"Width:5600}" +
"}}]]]";
解析結(jié)果為:

補(bǔ)充知識:將key名不帶雙引號的JSON字符串轉(zhuǎn)換成JSON對象的方法
根據(jù)json.org上面的描述,JSON對象是由對象成員組成,而成員是由key-value鍵值組成。
key值是一個字符串:
字符串由Unicode字符組成,用雙引號包圍,用反斜杠轉(zhuǎn)義??梢允菃蝹€字符。用法跟C或Java里的字符串的用法相似。
但是,在現(xiàn)實應(yīng)用中,很少有程序員知道JSON里的key需要用雙引號包圍,因為大多數(shù)的瀏覽器里并不需要使用雙引號。所以,為什么多此一舉要多寫兩個雙引號呢?
規(guī)范的例子:
{
"keyName" : 34
}
不規(guī)范的例子:
{
keyName : 34
}
雖然在瀏覽器里使用不規(guī)范的、不使用雙引號的寫法在瀏覽器里不會出現(xiàn)問題,但并不代表你可以在其它地方不會遇到問題,比如,你有一個字符串:
//字符串格式
'{ keyName : 34 }'
你想把它轉(zhuǎn)換成JSON對象。把JSON字符串轉(zhuǎn)換成JSON對象,需要使用 JSON.parse()方法,對于上面的這種key名上不帶雙引號的的JSON字符串,使用JSON.parse()解析時會報錯,無法解析。這就成了一個很麻煩的問題。所以說,盡量使用規(guī)范的預(yù)防還是有好處的,盡管大多數(shù)時候你不會遇到問題。
那么,對于key名不帶雙引號的JSON字符串,如何將它轉(zhuǎn)換成JSON對象呢?
最直接的方法是手工給key名加上雙引號。
如果你不像手工添加,可以使用函數(shù)全文搜索追加雙引號,比如下面的這段代碼:
json_string.replace(/(s*?{s*?|s*?,s*?)(['"])?([a-zA-Z0-9]+)(['"])?:/g, '$1"$3":');
eval('var json = new Object(' + json_string + ')');
最后,最簡單的一種方法是直接用eval()運(yùn)行它:
var obj = eval('(' + invalid_json + ')');
但這樣執(zhí)行時,你需要理解執(zhí)行的代碼是什么,因為如果它里面含有一些惡意程序,你這樣直接運(yùn)行很可能引起安全問題。
以上這篇Java 手動解析不帶引號的JSON字符串的操作就是小編分享給大家的全部內(nèi)容了,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot集成xxl-job實現(xiàn)超牛的定時任務(wù)的步驟詳解
XXL-JOB是一個分布式任務(wù)調(diào)度平臺,其核心設(shè)計目標(biāo)是開發(fā)迅速、學(xué)習(xí)簡單、輕量級、易擴(kuò)展,現(xiàn)已開放源代碼并接入多家公司線上產(chǎn)品線,開箱即用,本文給大家介紹了SpringBoot集成xxl-job實現(xiàn)超牛的定時任務(wù),需要的朋友可以參考下2023-10-10
Spring注解開發(fā)@Bean和@ComponentScan使用案例
這篇文章主要介紹了Spring注解開發(fā)@Bean和@ComponentScan使用案例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-09-09
Spring Cloud Gateway 緩存區(qū)異常問題及解決方案
最近在測試環(huán)境spring cloud gateway突然出現(xiàn)了異常,接下來通過本文給大家介紹Spring Cloud Gateway 緩存區(qū)異常問題解決方案,需要的朋友可以參考下2024-06-06
java遞歸實現(xiàn)復(fù)制一個文件夾下所有文件功能
這篇文章主要介紹了java遞歸實現(xiàn)復(fù)制一個文件夾下所有文件功能n次,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-08-08
使用@Builder導(dǎo)致無法創(chuàng)建無參構(gòu)造方法的解決
這篇文章主要介紹了使用@Builder導(dǎo)致無法創(chuàng)建無參構(gòu)造方法的解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-12-12
springboot中關(guān)于自動建表,無法更新字段的問題
這篇文章主要介紹了springboot中關(guān)于自動建表,無法更新字段的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-02-02

