1 需求说明
项目中遇到了一批不带引号的类JSON格式的字符串:
{Name:Heal,Age:20,Tag:[Coding,Reading]}
需要将其解析成JSON对象, 然后插入到Elasticsearch中, 当作Object类型的对象存储起来.
在对比了阿里的FastJson、Google的Gson, 没找到想要的功能 ( 可能是博主不够仔细, 有了解的童学留言告诉我下呀), 于是就自己写了个工具类, 用来实现此需求.
如果是带有引号的标准JSON字符串, 可直接通过上述2种工具进行解析, 使用方法可参考:
Java - 格式化输出JSON字符串的两种方式
2 解析代码
2.1 实现思路
代码的主要思路在注释中都有说明, 主要思路是:
(1) 借助Stack统计字符串首尾的[]、{}符号 —— []代表List, {}代表Map;
(2) 使用String#subString()方法缩减已解析的字符串;
(3) 使用递归解析内部的List、Map对象;
(4) 为了便于处理, 最小的key-value都解析成String类型.
需要注意的是: 要解析的字符串内部不要存在无意义的{、}、[、]符号, 否则会导致解析发生异常.
—— 暂时没想到好的兼容方法, 有想法的童学请直接留言.**
2.2 详细代码
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) {
// 带引号的字符串, 会将字符串当作key-value的一部分, 因此这类字符串推荐使用fastJson、Gson等工具转换
// 注意: String内部不要存在无意义的{、}、[、]符号 - 暂时没想到好的兼容方法
/*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> jsonArray;
Map jsonMap;
Object obj = null;
try {
obj = parseJson(sourceStr);
} catch (Exception e) {
System.out.println("出错啦: " + e.getMessage());
e.printStackTrace();
}
if (obj instanceof List) {
jsonArray = (List>) obj;
System.out.println("解析生成了List对象: " + jsonArray);
} else if (obj instanceof Map) {
jsonMap = (Map) obj;
System.out.println("解析生成了Map对象: " + jsonMap);
} else {
System.out.println("需要解析的字符串既不是JSON Array, 也不符合JSON Object!\n原字符串: " + sourceStr);
}
}
/**
* 解析 Json 格式的字符串, 封装为 List 或 Map 并返回
* 注意: (1) key 和 value 不能含有 ",", key 中不能含有 ":" —— 要分别用 "," 和 ":" 进行分隔
* (2) 要解析的字符串必须符合JSON对象的格式, 只对最外层的多层嵌套做了简单的处理,
* 复杂的如"{a:b},{x:y}"将不能完全识别 —— 正确的应该是"[{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, "{", "}");
// 借助栈来实现 "[]" 和 "{}" 的出入
Stack leftSymbolStack = new Stack<>();
Stack 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 内部还可能是连续的"{{}}"
parsedStr = simplifyStr(parsedStr, "{", "}");
} else {
throw new InvalidParameterException("要解析的字符串中存在不匹配的'[]'或'{}', 请检查!\n原字符串为: " + sourceStr);
}
// 保存解析的结果, jsonArray中可能只有String, 也可能含有Map
List jsonArray = new ArrayList<>();
Map jsonMap = new HashMap<>(16);
// 内部遍历、解析
innerParseByLoop(parsedStr, leftSymbolStack, rightSymbolStack, jsonArray, jsonMap);
// 判断jsonArray是否为空
if (jsonArray.size() > 0) {
return jsonArray;
} else {
return jsonMap;
}
}
/**
* 循环解析内部的List、Map对象
*/
private static void innerParseByLoop(String parsedStr, Stack leftSymbolStack, Stack rightSymbolStack,
List jsonArray, Map 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中的一个对象, 就需要确定第一个":"的位置 —— 可能存在多个":"
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));
// 解析过的串要一直跟进
parsedStr = parsedStr.substring(1);
key = key.substring(1);
}
// 判读和value是否以"["开头 —— 又是一个 List 对象 —— 递归解析
String value = keyValue.substring(index + 1);
if (value.startsWith("[")) {
int innerIndex = parsedStr.indexOf("]");
List innerList = (List) 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);
}
// 此内部存在对象, 内部的","已经解析完毕了, 要修正按照","切割的字符串数组, 并继续遍历
innerParseByLoop(parsedStr, leftSymbolStack, rightSymbolStack, jsonArray, jsonMap);
break;
}
// 判读和value是否以 "{" 开头 —— 又是一个 Map 对象 —— 递归解析
else if (value.startsWith("{")) {
int innerIndex = parsedStr.indexOf("}");
Map innerMap = (Map) 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);
}
// 此内部存在对象, 内部的","已经解析完毕了, 要修正按照","切割的字符串数组, 并继续遍历
innerParseByLoop(parsedStr, leftSymbolStack, rightSymbolStack, jsonArray, jsonMap);
break;
}
// 最后判断value尾部是否含有 "]" 或 "}"
else {
while (value.endsWith("]") || value.endsWith("}")) {
// 最右侧的字符
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中, 并创建新的对象
jsonArray.add(jsonMap);
jsonMap = new HashMap<>(16);
// 继续进行外层的解析
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中的一个串, 而非List中的一个Map, 则直接将其添加到List中即可
else {
jsonArray.add(keyValue);
}
}
// 遍历结束, 处理最后的符号问题 —— 判断左右栈是否匹配
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);
}
}
}
}
}
/**
* 判断字符串首尾有没有多余的、相匹配的 "[]" 和 "{}", 对其进行简化
*/
private static String simplifyStr(String sourceStr, String firstSymbol, String lastSymbol) {
while (sourceStr.startsWith(firstSymbol) && sourceStr.endsWith(lastSymbol)) {
String second = sourceStr.substring(1, 2);
// 如果第二个仍然是"["或"{", 再判断倒数第二个是不是"]"或"}" —— 说明长度至少为3, 不会发生 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\"]" +
"}}";
解析结果为:
(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}" +
"}}]]]";
解析结果为:
补充知识: 将key名不带双引号的JSON字符串转换成JSON对象的方法
根据json.org上面的描述,JSON对象是由对象成员组成,而成员是由key-value键值组成。
key值是一个字符串:
字符串由Unicode字符组成,用双引号包围,用反斜杠转义。可以是单个字符。用法跟C或Java里的字符串的用法相似。
但是,在现实应用中,很少有程序员知道JSON里的key需要用双引号包围,因为大多数的浏览器里并不需要使用双引号。所以,为什么多此一举要多写两个双引号呢?
规范的例子:
{ "keyName" : 34 }
不规范的例子:
{ keyName : 34 }
虽然在浏览器里使用不规范的、不使用双引号的写法在浏览器里不会出现问题,但并不代表你可以在其它地方不会遇到问题,比如,你有一个字符串:
//字符串格式
'{ keyName : 34 }'
你想把它转换成JSON对象。把JSON字符串转换成JSON对象,需要使用 JSON.parse()方法,对于上面的这种key名上不带双引号的的JSON字符串,使用JSON.parse()解析时会报错,无法解析。这就成了一个很麻烦的问题。所以说,尽量使用规范的预防还是有好处的,尽管大多数时候你不会遇到问题。
那么,对于key名不带双引号的JSON字符串,如何将它转换成JSON对象呢?
最直接的方法是手工给key名加上双引号。
如果你不像手工添加,可以使用函数全文搜索追加双引号,比如下面的这段代码:
json_string.replace(/(s*?{s*?|s*?,s*?)(['"])?([a-zA-Z0-9]+)(['"])?:/g, '$1"$3":');
eval('var json = new Object(' + json_string + ')');
最后,最简单的一种方法是直接用eval()运行它:
var obj = eval('(' + invalid_json + ')');
但这样执行时,你需要理解执行的代码是什么,因为如果它里面含有一些恶意程序,你这样直接运行很可能引起安全问题。
以上这篇Java 手动解析不带引号的JSON字符串的操作就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持脚本之家。