1. 程式人生 > 程式設計 >Java 手動解析不帶引號的JSON字串的操作

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,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,innerMap);

      // 清除最後的"}",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," +
      "\"tags\":[\"Java\",[\"Programming\"]" +
     "}}";

解析結果為:

Java 手動解析不帶引號的JSON字串的操作

(2) 不帶引號的測試:

// 測試字串: 
String sourceStr = "[[[{" +
     "{" +
      "Type:1," +
      "StoragePath:[{Name:/image/2019-08-01/15.jpeg," +
      "Width:140" +
     "}," +
     "{" +
      "Type:2," +
      "Inner:{DeviceID:44011200}," +
      "Test:[{ShotTime:2019-08-01 14:50:14}]," +
      "Width:5600}" +
     "}}]]]";

解析結果為:

Java 手動解析不帶引號的JSON字串的操作

補充知識:將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字串的操作就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支援我們。