Java - 手動解析不帶引號的JSON字符串


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<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 中不能含有 ":" —— 要分別用 "," 和 ":" 進行分隔
     *      (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<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 內部還可能是連續的"{{}}"
            parsedStr = simplifyStr(parsedStr, "{", "}");
        } else {
            throw new InvalidParameterException("要解析的字符串中存在不匹配的'[]'或'{}', 請檢查!\n原字符串為: " + sourceStr);
        }

        // 保存解析的結果, jsonArray中可能只有String, 也可能含有Map<String, Object>
        List<Object> jsonArray = new ArrayList<>();
        Map<String, Object> 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<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));
                        // 解析過的串要一直跟進
                        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);
                        }

                        // 此內部存在對象, 內部的","已經解析完畢了, 要修正按照","切割的字符串數組, 並繼續遍歷
                        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);
                        }

                        // 此內部存在對象, 內部的","已經解析完畢了, 要修正按照","切割的字符串數組, 並繼續遍歷
                        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<String>中的一個串, 而非List<Map>中的一個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}" +
                    "}}]]]";

解析結果為:

字符串解析結果-不帶引號

版權聲明

作者: 瘦風(https://healchow.com)

出處: 博客園 瘦風的博客(https://www.cnblogs.com/shoufeng)

感謝閱讀, 如果文章有幫助或啟發到你, 點個[好文要頂👆] 或 [推薦👍] 吧😜

本文版權歸博主所有, 歡迎轉載, 但 [必須在文章頁面明顯位置標明原文鏈接], 否則博主保留追究相關人員法律責任的權利.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM