Java – 手動解析不帶引號的JSON字元串

  • 2019 年 10 月 3 日
  • 筆記

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)

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

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