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 parses JSON strings without quotes
*
* @author Heal Chow
* @date 2019/08/13 11:36
*/
public class ParseJsonStrUtils {
public static void main(String[] args) {
// Quoted strings will treat the string as part of the key-value, so it is recommended to convert these strings using fastJson, Gson and other tools.
// Note: There should be no meaningless {,}, [,] symbols inside String - I didn't think of a good compatibility method for the time being
/*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 without quotes, multiple pairs of [] and {} will not affect the parsing
String sourceStr = "[[[{" +
"{" +
"Type:1," +
"StoragePath:[{Name:/image/2019-08-01/,DeviceID:4401120000130},{ShotTime:2019-08-01 14:44:24}]," +
"Width:140" +
"}," +
"{" +
"Type:2,StoragePath:9090/pic/2019_08_01/," +
"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("An error occurred: " + e.getMessage());
e.printStackTrace();
}
if (obj instanceof List) {
jsonArray = (List<Map<String, Object>>) obj;
System.out.println("The parsed list object is generated: " + jsonArray);
} else if (obj instanceof Map) {
jsonMap = (Map<String, Object>) obj;
System.out.println("The Map object was generated by parsing: " + jsonMap);
} else {
System.out.println("The string that needs to be parsed is neither JSON Array nor conforms to JSON Object!\nOriginal string: " + sourceStr);
}
}
/**
* Parses strings in Json format, encapsulate as List or Map and returns
* Note: (1) The key and value cannot contain ",", and the key cannot contain ":" - Use "," and ":" to separate them separately.
* (2) The string to be parsed must conform to the format of the JSON object, and only the outermost multi-layer nesting is simply processed.
* Complex things such as "{a:b},{x:y}" will not be fully recognized - the correct one should be "[{a:b},{x:y}]"
* @param sourceStr String surrounded by "[]" or "{}" at the beginning and end
* @return generated JsonObject
*/
public static Object parseJson(String sourceStr) throws InvalidParameterException {
if (sourceStr == null || "".equals(sourceStr)) {
return sourceStr;
}
// Determine whether there are redundant and matching "[]" and "{}" at the beginning and end of the string
String parsedStr = simplifyStr(sourceStr, "[", "]");
parsedStr = simplifyStr(parsedStr, "{", "}");
// Use the stack to achieve the entry and departure of "[]" and "{}"
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 may also be continuous inside "{{}}"
parsedStr = simplifyStr(parsedStr, "{", "}");
} else {
throw new InvalidParameterException("There is a mismatch '[]' or '{}' in the string to be parsed, please check!\nThe original string is: " + sourceStr);
}
// Save the parsed result. jsonArray may only have String, or may contain Map<String, Object>
List<Object> jsonArray = new ArrayList<>();
Map<String, Object> jsonMap = new HashMap<>(16);
// Internal traversal and analysis
innerParseByLoop(parsedStr, leftSymbolStack, rightSymbolStack, jsonArray, jsonMap);
// Determine whether jsonArray is empty
if (jsonArray.size() > 0) {
return jsonArray;
} else {
return jsonMap;
}
}
/**
* Looping to parse the internal List and Map objects
*/
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;
}
// Separate by ","
String[] allKeyValues = parsedStr.split(",");
if (allKeyValues.length > 0) {
// traverse, and parse according to ":"
out:
for (String keyValue : allKeyValues) {
// If the keyValue contains ":", it means that the keyValue is an object in List<Map>, you need to determine the location of the first ":" - there may be multiple ":"
int index = keyValue.indexOf(":");
if (index > 0) {
// Determine whether the key still starts with "{" or "[", if so, press the stack
String key = keyValue.substring(0, index);
while (key.startsWith("[") || key.startsWith("{")) {
leftSymbolStack.push(key.substring(0, 1));
// The parsed string must be followed up
parsedStr = parsedStr.substring(1);
key = key.substring(1);
}
// Whether the interpretation and value begins with "[" — another List object — recursive analysis
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);
// Clear the last "]" and determine whether ","
parsedStr = parsedStr.substring(innerIndex + 1);
if (parsedStr.indexOf(",") == 0) {
parsedStr = parsedStr.substring(1);
}
// This internal object has been parsed, and the string array cut according to "," must be corrected and continued to traverse
innerParseByLoop(parsedStr, leftSymbolStack, rightSymbolStack, jsonArray, jsonMap);
break;
}
// Whether the interpretation and value starts with "{" - another Map object - recursive analysis
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);
// Clear the last "}" and determine whether there is ","
parsedStr = parsedStr.substring(innerIndex + 1);
if (parsedStr.indexOf(",") == 0) {
parsedStr = parsedStr.substring(1);
}
// This internal object has been parsed, and the string array cut according to "," must be corrected and continued to traverse
innerParseByLoop(parsedStr, leftSymbolStack, rightSymbolStack, jsonArray, jsonMap);
break;
}
// Finally determine whether the value end contains "]" or "}"
else {
while (value.endsWith("]") || value.endsWith("}")) {
// The character on the far right
String right = value.substring(value.length() - 1);
// At this time, the top element of the stack
String top = leftSymbolStack.peek();
// If there is a matching one, the stack will be popped, otherwise it will be ignored
if (("}".equals(right) && "{".equals(top)) || ("]".equals(right) && "[".equals(top))) {
leftSymbolStack.pop();
value = value.substring(0, value.length() - 1);
jsonMap.put(key, value);
// Clear the last "}" and determine whether there is ","
parsedStr = parsedStr.substring(key.length() + 1 + value.length() + 1);
if (parsedStr.indexOf(",") == 0) {
parsedStr = parsedStr.substring(1);
}
// After parsing an object, add the element to the List and create a new object
jsonArray.add(jsonMap);
jsonMap = new HashMap<>(16);
// Continue to analyze the outer layer
continue out;
}
// If none of them match, it may be the last symbol of the source string
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 = "";
}
}
}
// If the keyValue does not contain ":", it means that the keyValue is just a string in List<String>, not a Map in List<Map>, then add it directly to the List
else {
jsonArray.add(keyValue);
}
}
// The traversal ends, dealing with the last symbol problem - determine whether the left and right stacks match
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("The passed string cannot be parsed into a JSON object!\nThe original string is: " + parsedStr);
}
}
}
}
}
/**
* Simplify whether there are redundant and matching "[]" and "{}" in the beginning and end of the string.
*/
private static String simplifyStr(String sourceStr, String firstSymbol, String lastSymbol) {
while (sourceStr.startsWith(firstSymbol) && sourceStr.endsWith(lastSymbol)) {
String second = sourceStr.substring(1, 2);
// If the second one is still "[" or "{", then determine whether the second last one is "]" or "}" - It means that the length is at least 3, and no IndexOutOfBoundsException will occur
if (second.equals(firstSymbol)) {
String penultimate = sourceStr.substring(sourceStr.length() - 2, sourceStr.length() - 1);
if (penultimate.equals(lastSymbol)) {
// Shorten the string to be parsed
sourceStr = sourceStr.substring(1, sourceStr.length() - 1);
} else {
break;
}
} else {
break;
}
}
return sourceStr;
}
}