Java - Manual parsing of unquoted JSON strings

Keywords: Java JSON Programming Google

Catalog

1 Requirement Description

A batch of JSON-like strings without quotation marks were encountered in the project:

{Name:Heal,Age:20,Tag:[Coding,Reading]}

It needs to be parsed into JSON objects and then inserted into Elastic search to be stored as objects of type Object.

After comparing FastJson of Ali with Gson of Google, I couldn't find the function I wanted (maybe the blogger was not careful enough and had a message about children's learning to tell me about it). So I wrote a tool class to fulfill this requirement.

If it is a standard JSON string with quotation marks, it can be parsed directly through the above two tools. The method of use can be referred to:
Java - Two ways to format output JSON strings

2 parse code

2.1 Implementation Ideas

The main ideas of the code are explained in the annotations. The main ideas are as follows:

(1) Stack is used to count the [], {} symbols at the beginning and end of the string -- [] for List and {} for Map.

(2) Use String subString () method to reduce the parsed string;

(3) The internal List and Map objects are parsed recursively.

(4) For ease of processing, the smallest key-value is resolved to String type.

It should be noted that there should be no meaningless {,}, [,] symbols inside the string to be parsed, otherwise the parsing will be abnormal.
—— I haven't thought of a good compatibility method for the time being. If you have any ideas about pediatrics, please leave a message directly. **

2.2 Detailed Code

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 Parsing Unquoted JSON Strings
 *
 * @author Heal Chow
 * @date 2019/08/13 11:36
 */
public class ParseJsonStrUtils {

    public static void main(String[] args) {

        // Quoted strings treat strings as part of key-value, so such strings are recommended to be converted using tools such as fastJson, Gson, and so on.
        // Note: There should be no meaningless {,}, [,] symbols inside String - no 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\"]" +
                           "}}";*/

        // Unquoted strings, the beginning and end of many pairs of [], {} do not affect parsing
        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("Error: " + e.getMessage());
            e.printStackTrace();
        }

        if (obj instanceof List) {
            jsonArray = (List<Map<String, Object>>) obj;
            System.out.println("Parsing generates List object: " + jsonArray);
        } else if (obj instanceof Map) {
            jsonMap = (Map<String, Object>) obj;
            System.out.println("Parsing generates Map object: " + jsonMap);
        } else {
            System.out.println("Strings that need to be parsed are neither JSON Array, And it doesn't fit. JSON Object!\n Original string: " + sourceStr);
        }
    }

    /**
     * Resolve Json-formatted strings, encapsulate them as List or Map, and return them
     * Note: (1) key and value cannot contain ",", key cannot contain ":" - separated by "," "and":", respectively.
     *      (2) The string to be parsed must conform to the JSON object format, and only the outermost multi-layer nesting is handled simply.
     *          Complex examples such as {a:b},{x:y} will not be fully recognized - the correct ones should be [{a:b},{x:y}].
     * @param sourceStr A string surrounded by "[]" or "{}"
     * @return Generated JsonObject
     */
    public static Object parseJson(String sourceStr) throws InvalidParameterException {
        if (sourceStr == null || "".equals(sourceStr)) {
            return sourceStr;
        }

        // Determine whether there are redundant matching "[]" and "{}" at the beginning and end of a string
        String parsedStr = simplifyStr(sourceStr, "[", "]");
        parsedStr = simplifyStr(parsedStr, "{", "}");

        // Implementing the entry and exit of "[]" and "{}" with the help of stack
        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);

            // The interior of parsedStr may also be a continuous'{}}'
            parsedStr = simplifyStr(parsedStr, "{", "}");
        } else {
            throw new InvalidParameterException("There is a mismatch in the string to parse'[]'or'{}', Please check!\n The original string is: " + sourceStr);
        }

        // Save the result of parsing. jsonArray may contain only String, or 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);

        // Judging whether JSON Array is empty
        if (jsonArray.size() > 0) {
            return jsonArray;
        } else {
            return jsonMap;
        }
    }

    /**
     * Loop parsing 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 according to "."
        String[] allKeyValues = parsedStr.split(",");
        if (allKeyValues.length > 0) {

            // Traverse and parse separately according to ":"
            out:
            for (String keyValue : allKeyValues) {

                // If the keyValue contains ":", indicating that the keyValue is an object in List < Map>, it is necessary 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 "[", and if so, the stack
                    String key = keyValue.substring(0, index);
                    while (key.startsWith("[") || key.startsWith("{")) {
                        leftSymbolStack.push(key.substring(0, 1));
                        // The parsed string should be followed up all the time.
                        parsedStr = parsedStr.substring(1);
                        key = key.substring(1);
                    }

                    // Whether Interpretation and value Start with "[" - Another List Object - Recursive Resolution
                    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 if it exists.
                        parsedStr = parsedStr.substring(innerIndex + 1);
                        if (parsedStr.indexOf(",") == 0) {
                            parsedStr = parsedStr.substring(1);
                        }

                        // This internal object, the internal "," has been parsed, to correct according to "," the cut string array, and continue to traverse.
                        innerParseByLoop(parsedStr, leftSymbolStack, rightSymbolStack, jsonArray, jsonMap);
                        break;
                    }

                    // Whether Interpretation and value Begin with'{''-- Another Map Object -- Recursive Resolution
                    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 if it exists.
                        parsedStr = parsedStr.substring(innerIndex + 1);
                        if (parsedStr.indexOf(",") == 0) {
                            parsedStr = parsedStr.substring(1);
                        }

                        // This internal object, the internal "," has been parsed, to correct according to "," the cut string array, and continue to traverse.
                        innerParseByLoop(parsedStr, leftSymbolStack, rightSymbolStack, jsonArray, jsonMap);
                        break;
                    }

                    // Finally, determine whether the value tail contains "]" or "}"
                    else {
                        while (value.endsWith("]") || value.endsWith("}")) {
                            // The rightmost character
                            String right = value.substring(value.length() - 1);
                            // At this point, the top element of the stack
                            String top = leftSymbolStack.peek();
                            // If there is a match, then stack, otherwise ignore
                            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 if it exists.
                                parsedStr = parsedStr.substring(key.length() + 1 + value.length() + 1);
                                if (parsedStr.indexOf(",") == 0) {
                                    parsedStr = parsedStr.substring(1);
                                }

                                // When an object is parsed, the element is added to the List and a new object is created.
                                jsonArray.add(jsonMap);
                                jsonMap = new HashMap<>(16);

                                // Continue with the analysis of the outer layer
                                continue out;
                            }

                            // If they do not match, they 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 ":", which means that the keyValue is only a string in List < String > but not a Map in List < Map >, it can be added directly to List.
                else {
                    jsonArray.add(keyValue);
                }
            }

            // The end of traversal and the final symbolic problem: judging whether the left stack matches the right stack or not
            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 incoming string cannot be parsed JSON object!\n The original string is: " + parsedStr);
                    }
                }
            }
        }
    }

    /**
     * Simplify the string by judging whether there are redundant matching "[]" and "{}" at 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 is still "[" or "{", then judge whether the penultimate is "]" or "}" - indicating that the length is at least 3, there will be no IndexOutOfBoundsException.
            if (second.equals(firstSymbol)) {
                String penultimate = sourceStr.substring(sourceStr.length() - 2, sourceStr.length() - 1);
                if (penultimate.equals(lastSymbol)) {
                    // Shorten the string to parse
                    sourceStr = sourceStr.substring(1, sourceStr.length() - 1);
                } else {
                    break;
                }
            } else {
                break;
            }
        }
        return sourceStr;
    }

}

2.3 Test Sample

(1) Quoted test:

// Test 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\"]" +
                   "}}";

The analytical results are as follows:

(2) Test without quotation marks:

// Test string: 
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}" +
                    "}}]]]";

The analytical results are as follows:


Copyright Statement

Authors: Thin Wind (https://healchow.com)

Origin: Blog Garden Thin Wind Blog (https://www.cnblogs.com/shoufeng)

Thank you for reading. If the article helps or inspires you, click[ Good writing should go to the top (vii) ] Or[ Recommendations (1) ] Let's go.

Copyright of this article belongs to the blogger. Reprint is welcome, but [link of the original text must be marked clearly on the article page]. Otherwise, the blogger reserves the right to pursue the legal responsibility of the relevant personnel.

Posted by Kathy on Mon, 19 Aug 2019 22:45:46 -0700